引言
自己覺得對二叉樹了解的不是很多,所以想專門練習一下這方面的問題,劍指Offer中有一道題是將二叉搜索樹轉換為雙向鏈表,開始的時候照着書上的思路去做,最后發現問題很多,看來這本書也有很多問題啊,原因是Java和C++還是有很多不同的,特別是對對象的引用方面。自己在紅色部分標注出了問題的所在,希望能給之后遇到同樣問題的人一點幫助
分析問題
首先需要明白二叉搜索樹也是一種排序的數據結構,它的中序遍歷就是一個不遞減的順序排列
所以如果要轉換成一個排序好的雙向鏈表,那么僅需要改變原來指向左子節點和右子節點的指針,讓他們分別指向前節點和后節點即可,如圖所示

調整指針
原先指向左子節點的指針調整為鏈表中指向前一個節點的指針
原先指向右子節點的指針調整為鏈表中指向后一個節點的指針
如何調整
考慮根節點和左右子樹的根本情況,因為如果用遞歸,這種根本情況考慮就可以去將同樣的方法用到左右子樹上

對於這種基本情況,可以分成三個部分來看,根節點10,左子樹,右子樹,需要做的就是將10與左子樹中的最大值連起來,然后把10與右子樹中的最小值連起來
現在有個問題就是我們並不知道左子樹中的最大值和右子樹中的最小值,如果我們知道就好了。但是想到遞歸,遞歸到左子樹中,如果左子樹已轉換為雙向鏈表,那么雙向鏈表的最后一個節點就是我們想要的,而右子樹中的第一個節點也是我們想要的
轉換代碼
public static BinaryTreeNode baseconvert(BinaryTreeNode root, BinaryTreeNode lastNode) {
if (root == null)
return lastNode;
BinaryTreeNode current = root;
if (current.leftNode != null)
lastNode=baseconvert(current.leftNode, lastNode);
current.leftNode = lastNode;
if (lastNode != null)
lastNode.rightNode = current;
lastNode = current;
if (current.rightNode != null)
lastNode=baseconvert(current.rightNode, lastNode);
return lastNode;
}
要說明的一點是,劍指Offer書上是用C++寫的,Java寫的時候有點不同,就是baseconvert也要指定返回值,不然會報nullpointer的錯誤,估計是因為java中只是對象的引用,不然每次返回的lastNode都是空
上面的代碼中有兩個參數,一個是根節點,一個是已經轉換好的鏈表的最后一個節點,因為二叉搜索樹中序遍歷的特性,當遍歷到根節點的時候,左子樹已經排好序了,所以會有一個左子樹已經轉換好的鏈表,而這個鏈表的最后一個節點即是我們需要和根節點左連的節點
最開始的時候lastNode為null
current為當前子樹的根節點
如果左子樹存在,那么轉換左子樹,遞歸下去,遞歸返回之后,說明找到了鏈表的第一個節點,也就是4那個節點,將4的前面節點置為null,此時current為4那個節點,這個時候由於6的4這個左子樹已遍歷完了,所以需要往上層返回,返回之前需要將current賦值給lastNode,說明4這個左子樹的最后一個節點就是4
由於往上返回了一層,此時的current已經是6了,將6的左節點賦值為之前返回的4,判斷之前返回的lastNode是否為null,不為空說明需要把根節點和lastNode連起來,其實lastNode為null的情況就只有第一個節點會出現,其他的時候都不會出現。現在已排好序的包括6的左子樹以及6本身了,所以此時的lastNode為6
6和4的雙向連接就完成了,由於6的右子樹存在,又會遞歸到右邊子樹去,由於8不存在左右子樹,遞歸下去一層之后current就是8這個節點,但它的左孩子為空,所以不會左邊遞歸下去,將8的左連接與之前的lastNode連接起來,建立雙向連接的一條連接,然后由於lastNode不為空,所以又把lastNode的右連接與8連接起來,至此雙向連接建立,此時lastNode為8
所以468都已排好序,此時lastNode為8,返回到上一層,也就是根節點10了,在這一層current為10,將current的左連接與lastNode連接起來,如果lastNode存在,將lastNode的右連接與10連接一起,以此建立雙向連接。至此就將根節點和左子樹都連接起來了,然后就是去轉換右子樹,現在的lastNode為10,current為14,14有左孩子,所以需要遞歸到下一層,下一層的current為12,12沒有左孩子,所以不用在坐遞歸,所以12是12這棵子樹轉換成雙向鏈表的最左邊的節點,將lastNode與12連接,也就是10與12連接,此時的lastNode就變成了12,再將12的右子樹遞歸,由於沒有右子樹,所以直接返回到上一層,上一層的current是14,14與已排好序的lastNode連接,也就是12與14連接,然后lastNode變為14,遞歸到14的右子樹,也就current變為16,16再遞歸左子樹,無左子樹,將16與14連接,此時的lastNode變為16,遞歸右子樹,無右子樹,所以整個遞歸工作完成
找到頭節點
之前的轉換只是把引用乾坤大挪移了一下,其實可以發現引用的個數並沒有變化,而且頭尾節點只有一個出度的引用,而現在我們如果要使用這個雙向鏈表,需要找到頭節點
public static BinaryTreeNode convert(BinaryTreeNode root) {
BinaryTreeNode lastNode = null;
lastNode=baseconvert(root, lastNode);
BinaryTreeNode headNode = lastNode;
while (headNode.leftNode != null)
headNode = headNode.leftNode;
return headNode;
}
測試代碼
public static void main(String[] args) {
// TODO Auto-generated method stub
BinaryTreeNode root = new BinaryTreeNode(10);
BinaryTreeNode six=new BinaryTreeNode(6);
BinaryTreeNode four=new BinaryTreeNode(4);
BinaryTreeNode eight=new BinaryTreeNode(8);
BinaryTreeNode fourteen=new BinaryTreeNode(14);
BinaryTreeNode twelve=new BinaryTreeNode(12);
BinaryTreeNode sixteen=new BinaryTreeNode(16);
root.leftNode=six;
root.rightNode=fourteen;
six.leftNode=four;
six.rightNode=eight;
four.leftNode=null;
four.rightNode=null;
eight.leftNode=null;
eight.rightNode=null;
fourteen.leftNode=twelve;
fourteen.rightNode=sixteen;
twelve.leftNode=null;
twelve.rightNode=null;
sixteen.rightNode=null;
sixteen.leftNode=null;
BinaryTreeNode result=convert(root);
// BinaryTreeNode result=baseconvert(root, null);
while (result!=null) {
System.out.println(result.data);
result=result.rightNode;
}
