【劍指offer】面試題36:二叉搜尋樹與雙向連結串列
題目:輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列。要求不能建立任何新的結點,只能調整樹中結點指標的指向。
比如如下圖中的二叉搜尋樹,則輸出轉換之後的排序雙向連結串列為:
在二叉樹中,每個結點都有兩個指向子節點的指標。在雙向連結串列中,每個結點也有兩個指標,他們分別指向前一個結點和後一個結點。由於這兩種結點的結構相似,同時二叉搜尋樹也是一種排序的資料結構,因此在理論上有可能實現二叉搜尋樹和排序的雙向連結串列的轉換。在搜尋二叉樹中,左子結點的值總是小於父節點的值,右子節點的值總是大於父節點的值。因此我們在轉換稱排序的雙向連結串列時,原先指向的左子結點的指標調整為連結串列中指向前一個結點的指標,原先指向右子節點的指標調整為倆表為指向後一個結點的指標
由於要求轉換之後的連結串列是排好序的,我們可疑中序遍歷樹中的每一個結點,這是因為中序遍歷演算法的特點是按照從小到達的順序遍歷二叉樹的每一個結點。當遍歷到根節點的時候,我們把樹看成3部分:值為10的結點,根節點為6的左子樹、根節點為14的右子樹。根據排序連結串列的定義,值為10的結點將和它的左子樹的最大的一個結點(即值為8的結點)連結起來,同時它還將和右子樹最小的結點(即值為12的結點)連結起來,如下圖所示:
將上訴過程轉換為程式碼如下:
解題思路:
1
.將左子樹構造成雙鏈表,並返回連結串列頭節點;
2
.定位至左子樹雙鏈表最後一個節點;
3
.如果左子樹連結串列不為空的話,將當前root追加到左子樹連結串列;
4
.將右子樹構造成雙鏈表,並返回連結串列頭節點;
5
.如果右子樹連結串列不為空的話,將該連結串列追加到root節點之後;
6
.根據左子樹連結串列是否為空確定返回的節點。
/** * 輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列。 * 要求不能建立任何新的結點,只能調整樹中結點指標的指向 * * 思路:二叉搜尋樹的中序遍歷 */ public class ConvertTreeToDoubleLinkedList_Method1 { public class TreeNode{ int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val){ this.val = val; } } public TreeNode Convert(TreeNode root){ if(root == null){ return null; } if(root.left == null && root.right == null){ return root; } // 1、將左子樹構造成雙鏈表,並返回連結串列頭節點 TreeNode left = Convert(root.left); TreeNode p = left; // 2、定位至左子樹雙向連結串列中的最後一個節點 while(p != null && p.right != null){ p = p.right; } // 3、如果左子樹連結串列不為空的話,將當前root追加到左子樹連結串列的末尾 if(left != null){ p.right = root; root.left = p; } // 4、將右子樹構造成雙向連結串列,並返回連結串列頭節點 TreeNode right = Convert(root.right); // 5、如果右子樹連結串列不為空的話,將該連結串列追加到 root 節點之後 if(right != null){ right.left = root; root.right = right; } return left != null ? left : root; } }
上面的程式碼雖然不是很簡潔,但是比較簡單易懂。因為樹的指標表達方式是用 right 和 left ,而連結串列表示指標一般用 next,所以可能會有所不適應。但是隻要記住遞迴時,node 的 right 節點是比它大的,node 的 left 節點是比它小的。使用中序遍歷,只需要找到根節點和它左子樹中最後一個節點以及右子樹中的第一個節點之間的關係,然後遞迴這個過程即可。
下面再提供一種牛客網上比較簡潔的程式碼實現方法(我感覺理解起來沒有上面的那種直觀):
/**
* 輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的雙向連結串列。
* 要求不能建立任何新的結點,只能調整樹中結點指標的指向
*
* 思路:二叉搜尋樹的中序遍歷
*/
public class ConvertTreeToDoubleLinkedList {
public class TreeNode{
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val){
this.val = val;
}
}
// 雙向連結串列中的左右頭節點
TreeNode leftHead = null;
TreeNode rightHead = null;
public TreeNode Convert(TreeNode root){
// 遞迴終止條件:遞迴呼叫葉子節點的左右子節點,返回null
if(root == null){
return null;
}
// 第一次執行時,它會使最左葉子節點成為連結串列中的頭節點
Convert(root.left);
if(rightHead == null){
leftHead = rightHead = root;
}else{
// 把根節點插入到雙向連結串列右邊,rightHead向後移動
rightHead.right = root;
root.left = rightHead;
rightHead = root;
}
// 把右子節點也插入到雙向連結串列(rightHead已確定,直接插入即可)
Convert(root.right);
// 返回左邊的頭節點
return leftHead;
}
}