二叉搜尋樹轉換為雙向連結串列的Java實現 出現的一些問題及解決
題目
二叉搜尋樹轉換為雙向排序連結串列,要求不能新增任何新的結點,只能調整樹中節點的指向。
思路
二叉搜尋樹的左子樹中結點的值總是小於根結點,右子樹中結點的值總是大於根結點,所以二叉搜尋樹的中序遍歷結果就是我們最終想要得到的連結串列順序,如圖1:
所以我們在轉換成雙向排序連結串列時,原先指向左子結點的指標調整為指向連結串列前一個結點的指標,原先指向右子結點的指標調整為指向連結串列後一個結點的指標。 由於需要轉換之後需要是排好序的,我們可以吧二叉搜尋樹看成三部分,左子樹,根結點右子樹。我們需要先把左子樹轉換成排序二叉樹,再在左子樹的最後一個結點接上根結點,然後將右子樹轉換成連結串列,將右子樹轉換成的連結串列接在根結點之後,至於左右子樹轉換成連結串列,我們就可以採用遞迴來實現。解決這個問題的關鍵就是如何找到轉換後連結串列的最後一個結點。如圖2:
實現
構造二叉樹的Java資料結構:
public class BinaryTreeNode {
int val; //節點值
BinaryTreeNode left; //左子節點指標
BinaryTreeNode right; //右子結點指標
//構造方法
public BinaryTreeNode(){}
public BinaryTreeNode(int val){
this.val=val;
}
}
測試主方法:
public static void main(String[] args) { //構造二叉排序樹 BinaryTreeNode root=new BinaryTreeNode(10); root.left=new BinaryTreeNode(6); root.right=new BinaryTreeNode(14); root.left.left=new BinaryTreeNode(4); root.left.right=new BinaryTreeNode(8); root.right.left=new BinaryTreeNode(12); root.right.right=new BinaryTreeNode(16); //呼叫轉換方法 BinaryTreeNode linkedList = treeToLinkedList(root); }
實現一
我們傳遞一個lastNode指標,這個指標始終指向已構造好的連結串列的最後一個結點,左子樹構造好時,指向左子樹最後一個結點,然後指向根結點,最後指向右子樹的最後一個結點,仿造《劍指offer》上的C++版實現,如下:
/** * 定義:轉換後left表示pre,right表示next * @param root 根結點 * @return */ public static BinaryTreeNode treeToLinkedList(BinaryTreeNode root) { if(root==null) return root; //連結串列尾節點 BinaryTreeNode lastNode=null; //轉換 convert(root,lastNode); //尋找返回的頭結點 BinaryTreeNode head=lastNode; while(head.left!=null) { head = head.left; } return head; } /** * @param root 當前子樹的根結點 * @param last 儲存當前連結串列的最後一個指標 * @return */ public static void convert(BinaryTreeNode root,BinaryTreeNode last) { if(root==null) return; BinaryTreeNode p=root; //轉換左子樹 if(root.left!=null) convert(root.left,last); //根結點的pre指向左子樹的last,左子樹的last的next指向根結點 p.left=last; if(last!=null) last.right=p; //last指向根結點 last=p; //轉換右子樹 if(root.right!=null) convert(root.right,last); }
出現的問題
執行上面的程式碼,發現出現空指標異常,原因是因為程式執行結束的時候lastNode為null,一步步跟進去除錯發現,當子結點更新了last 的值之後,返回上層結點的時候,並不能將上層結點的變數last進行更新。這是因為當呼叫方法的時候,傳遞進去的last引數,只是傳遞的副本,當子方法中引數副本指向新的結點時,原來方法中的last並不會更新。 而《劍指offer》中能夠實現的原因正是體現了C++與Java的區別,《劍指offer》中該方法為:
void ConverNode(BinaryTreeNode* pNode, BinaryTreeNode** pLastNodeInList)
{
... ...
}
這裡的引數列表中 pLastNodeList變數使用的是“指向指標的指標”,也就是說這個變數儲存的是last結點所在地址的指標,當這個變數在子結點方法中被修改時,自然也會引起上次方法的改變。因為Java中沒有 指向指標的指標,所以就不能這樣來實現。
實現二
因為下層修改了last變數不能傳遞到上層方法中,所以我們可以通過返回值可以傳遞last變數的值,我們對上面的程式碼進行如下修改,實現如下:
/**
* 定義:轉換後left表示pre,right表示next
* @param root 根結點
* @return
*/
public static BinaryTreeNode treeToLinkedList(BinaryTreeNode root) {
if(root==null) return root;
//連結串列尾節點
BinaryTreeNode lastNode=null;
//轉換
lastNode=convert(root);
//尋找返回的頭結點
BinaryTreeNode head=lastNode;
while(head.left!=null) {
head = head.left;
}
return head;
}
/**
* @param root 當前子樹的根結點
* @return 返回當前連結串列的最後一個結點
*/
public static BinaryTreeNode convert(BinaryTreeNode root) {
if(root==null) return null;
BinaryTreeNode p=root;
BinaryTreeNode last=null;
//轉換左子樹
if(root.left!=null) last=convert(root.left);
//根結點的pre指向左子樹的last,左子樹的last的next指向根結點
p.left=last;
if(last!=null) last.right=p;
//last指向根結點
last=p;
//轉換右子樹
if(root.right!=null) last=convert(root.right);
return last;
}
出現的問題
按照我們的想法,通過返回值就可以將下層的變數返回給上層方法了,但是執行測試方法,結果依然不對,不是我們想要的連結串列。經過對程式碼的一步步除錯發現了另一個問題,當左子樹與根結點跟新了last變數之後,訪問右子樹的時候,右子樹卻得不到之前的last變數指向的結點。這是因為下層方法無法得到上層結果last變數,和之前出現的問題是相反的,所以我們又對程式碼進行了第二次修改。
最終正確的實現
因為上層得不到下層的更新結果,我們採用了返回值的方式進行解決,但是下層又得不到上層的更新結果,我們採用引數列表來解決這個問題,通過引數將上層更新結果傳遞到下層中去,就能完成這個問題。
/**
* 定義:轉換後left表示pre,right表示next
* @param root 根結點
* @return
*/
public static BinaryTreeNode treeToLinkedList(BinaryTreeNode root) {
if(root==null) return root;
//連結串列尾節點
BinaryTreeNode lastNode=null;
//轉換
lastNode=convert(root,lastNode);
//尋找返回的頭結點
BinaryTreeNode head=lastNode;
while(head.left!=null) {
head = head.left;
}
return head;
}
/**
* 返回當前已轉換好的連結串列的尾結點
* @param root 當前子樹的根結點
* @param last 儲存當前連結串列的最後一個指標
* @return 返回當前連結串列的最後一個結點
*/
public static BinaryTreeNode convert(BinaryTreeNode root,BinaryTreeNode last) {
if(root==null) return null;
BinaryTreeNode p=root;
//轉換左子樹
if(root.left!=null) last=convert(root.left,last);
//根結點的pre指向左子樹的last,左子樹的last的next指向根結點
p.left=last;
if(last!=null) last.right=p;
//last指向根結點
last=p;
//轉換右子樹
if(root.right!=null) last=convert(root.right,last);
return last;
}
執行測試方法,獲得了正確的雙向排序連結串列。