本文參考自《劍指offer》一書,代碼采用Java語言。
題目
輸入一棵二叉搜索樹,將該二叉搜索樹轉換成一個排序的雙向鏈表。要求不能創建任何新的結點,只能調整樹中結點指針的指向。
思路
二叉搜索樹、排序鏈表,想到使用中序遍歷。
要實現雙向鏈表,必須知道當前結點的前一個結點。根據中序遍歷可以知道,當遍歷到根結點的時候,左子樹已經轉化成了一個排序的鏈表了,根結點的前一結點就是該鏈表的最后一個結點(這個結點必須記錄下來,將遍歷函數的返回值設置為該結點即可),鏈接根結點和前一個結點,此時鏈表最后一個結點就是根結點了。再處理右子樹,遍歷右子樹,將右子樹的最小結點與根結點鏈接起來即可。左右子樹的轉化采用遞歸即可。
大概思想再理一下:首先想一下中序遍歷的大概代碼結構(先處理左子樹,再處理根結點,之后處理右子樹),假設左子樹處理完了,就要處理根結點,而根結點必須知道左子樹的最大結點,所以要用函數返回值記錄下來;之后處理右子樹,右子樹的最小結點(也用中序遍歷得到)要和根結點鏈接。
注意搞清楚修改后的中序遍歷函數的意義(見代碼注釋)
測試算例
1.功能測試(一個結點;左右斜樹;完全二叉樹;普通二叉樹)
2.特殊測試(根結點為null)
Java代碼
//題目:輸入一棵二叉搜索樹,將該二叉搜索樹轉換成一個排序的雙向鏈表。要求
//不能創建任何新的結點,只能調整樹中結點指針的指向。
public class ConvertBinarySearchTree {
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
public TreeNode convert(TreeNode head) {
if(head==null)
return head;
TreeNode lastNodeInList=null;
lastNodeInList=convertHelper(head,lastNodeInList);
TreeNode firstNodeInList=lastNodeInList;
while(firstNodeInList.left!=null) {
firstNodeInList=firstNodeInList.left;
}
return firstNodeInList;
}
//將以node為根結點的樹轉化為排序鏈表,鏈表頭部與lastNode鏈接,並返回最后一個結點
private TreeNode convertHelper(TreeNode node,TreeNode lastNode) {
//處理左子樹,獲得最大結點
if(node.left!=null)
lastNode=convertHelper(node.left, lastNode);
//鏈接最大結點和根結點
node.left=lastNode;
if(lastNode!=null)
lastNode.right=node;
//處理右子樹
lastNode=node;
if(node.right!=null)
lastNode=convertHelper(node.right, lastNode);
return lastNode;
}
}
上面的代碼是參考《劍指OFFER》寫的,下面的代碼是復習時重新寫過的,思路比較簡潔一點。非遞歸方法的核心是中序遍歷的非遞歸實現。
public class Solution {
/*
* 遞歸版本
* 1.已知函數返回的是轉換好的雙向鏈表頭結點
* 2.左子樹處理完后與根結點連接
* 3.右子樹處理,也與根結點連接
* 4.最后返回頭結點
*/
public TreeNode Convert(TreeNode root) {
if (root == null)
return root;
// 處理左子樹,獲得左子樹鏈表的頭結點
TreeNode left = Convert(root.left);
TreeNode p = left;
if (left != null) {
// 找到左子樹鏈表的末尾結點
while (p.right != null)
p = p.right;
// 連接結點
p.right = root;
root.left = p;
}
// 處理右子樹,獲得右子樹鏈表的頭結點
TreeNode right = Convert(root.right);
// 連接結點
if (right != null) {
root.right = right;
right.left = root;
}
return left == null ? root : left;
}
/* 非遞歸版本
* 1.利用非遞歸中序遍歷來連接結點
*/
public TreeNode Convert1(TreeNode root) {
TreeNode head = null;
TreeNode pre = null;
LinkedList<TreeNode> stack = new LinkedList<>();
while (root != null || !stack.isEmpty()) {
// 把root當作指針使用
while (root != null) {
stack.push(root);
root = root.left;
}
TreeNode node = stack.pop();
if (head == null) {
head = node;
pre = node;
} else {
node.left = pre;
pre.right = node;
pre = node; // 別漏寫了
}
root = node.right;
}
return head;
}
}
收獲
題目較復雜時,不要慌。這道題和中序遍歷有關,把樹分為三部分:根結點、左子樹和右子樹,思考在中序遍歷中根結點應該如何處理,這是關鍵——要將左子樹的最大結點、根結點、右子樹的最小結點鏈接起來。左右子樹的處理是相同的,因此采用遞歸。
