二叉查找樹轉雙向鏈表JAVA實現

1、問題描述java

輸入一棵二叉搜索樹,如今要將該二叉搜索樹轉換成一個排序的雙向鏈表。並且在轉換的過程當中,不能建立任何新的結點,只能調整樹中的結點指針的指向來實現。node

 

2、實現思路算法

在二叉搜索樹中,每一個結點都有兩個分別指向其左、右子樹的指針,左子樹結點的值老是小於父結點的值,右子樹結點的值老是大於父結點的值。而在雙向鏈表中,每一個結點也有兩個指針,它們分別指向前一個結點和後一個結點。因此這兩種數據結構的結點是一致,二叉搜索樹之因此爲二叉搜索樹,雙向鏈表之因此爲雙向鏈表,只是由於兩個指針的指向不一樣而已,經過改變其指針的指向來實現是徹底可能的。數據結構

 

例如以下的二叉搜索樹,app

 

若採用中序遍歷,其遍歷順序爲1-2-3-4-5-6-7,經過適當的指針變換操做,可變成的雙向有序鏈表以下:函數

 

 

從上圖,咱們能夠看出,爲了減小指針的變換次數,並讓操做更加簡單,在轉換成排序雙向鏈表時,原先指向左子結點的指針調整爲鏈表中指向前一個結點的指針,原先指向右子結點的指針調整爲鏈表中指向下一個結點的指針。例如對於上面的值爲2的指點,調整後,它的前一個結點爲1,後一個結點爲3,而結點2的左子結點原本就爲1,右子結點原本就爲3.測試

 

對於樹的操做,一般是在遍歷樹的各個結點的過程當中,經過對結點實施某些操做來完成的,這個算法也不例外。因爲要求轉換後的雙向鏈表也是有序的,而咱們從上面也能夠看到,當咱們以中序遍歷二叉搜索樹時,其遍歷的結點就是有序的,因此在這裏我位採用的遍歷順序應該是中序。this

 

那麼咱們應該如何調整指針,讓二叉搜索樹變成一個雙向有序鏈表呢?當遍歷到根結點時,咱們能夠把樹當作三個部分:根結點,根的左子樹和根的右子樹。如上圖的二叉排序樹,就分紅了根結點四、以結點2爲根的左子對和以結點6爲根的右子樹。從變換的鏈表中咱們能夠看到,應當把結點4的left指針指向結點3,把結點3的right指針指向結點4,而因爲咱們採用的是中序遍歷,因此當咱們遍歷到結點4時,結點4的左子樹已經轉化爲一個有序的雙向鏈表,而結點3是這個已經轉化的雙向鏈表的尾結點,因此咱們應該用一個變量last_node來保存最後一個結點的指針,以便在與根結點連續時使用。而後把這個變量last_node的值更新爲指向根結點4。對於結點4的右子樹,採起類似的操做。至於具體的實現,咱們只須要對全部的子樹遞歸地執行上述操做便可。其操做過程以下:spa

 

a] view plain copy.net

在CODE上查看代碼片派生到個人代碼片

  1. /** 
  2. public class TreeNode { 
  3.     int val = 0; 
  4.     TreeNode left = null; 
  5.     TreeNode right = null; 
  6.  
  7.     public TreeNode(int val) { 
  8.         this.val = val; 
  9.  
  10.     } 
  11.  
  12. */  
  13. public class Solution {  
  14.     private TreeNode head=null;  
  15.     private TreeNode tail=null;  
  16.     public TreeNode Convert(TreeNode pRootOfTree) {  
  17.         visit(pRootOfTree);  
  18.         return head;  
  19.     }  
  20.     public void visit(TreeNode root) {    
  21.         if (root == null) {    
  22.             return;    
  23.         }    
  24.         visit(root.left);    
  25.         createList(root);    
  26.         visit(root.right);    
  27.     }    
  28.     public void createList(TreeNode cur){    
  29.         cur.left=tail;//把當前的節點接到鏈表的尾部     
  30.         if(tail!=null){//雙向鏈接     
  31.             tail.right=cur;     
  32.         }else{     
  33.             head=cur;     
  34.         }     
  35.         tail=cur;//更新尾結點爲當前結點,或者說:尾結點後移     
  36.     }    

 

 

 

 

時間複雜度與空間複雜度

該算法首先從根要點一直向左走,找到最左邊的結點,其時間複雜度爲O(logN),而後對二叉排序樹中的每一個結點遍歷一次,進行指針變換,其時間複雜度爲O(N),因此總的時間複雜度爲O(N)。

 

至於空間複雜度,因爲ConvertNode函數進行遞歸調用,其函數有兩個開參,而函數棧中的函數調用層數不會超過樹高,因此其空間複雜度爲O(logN)。

 

 

分析問題

   

首先須要明白二叉搜索樹也是一種排序的數據結構,它的中序遍歷就是一個不遞減的順序排列

因此若是要轉換成一個排序好的雙向鏈表,那麼僅須要改變原來指向左子節點和右子節點的指針,讓他們分別指向前節點和後節點便可,如圖所示

   

   

調整指針

   

原先指向左子節點的指針調整爲鏈表中指向前一個節點的指針

   

原先指向右子節點的指針調整爲鏈表中指向後一個節點的指針

   

如何調整

   

考慮根節點和左右子樹的根本狀況,由於若是用遞歸,這種根本狀況考慮就能夠去將一樣的方法用到左右子樹上

   

   

對於這種基本狀況,能夠分紅三個部分來看,根節點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;

}

相關文章
相關標籤/搜索