二叉樹介紹

二叉樹能夠用來作什麼?node

答:能夠搜索、排序程序員

但是,排序有快速排序、歸併排序,查找有二分法、直接遍歷等,那麼爲何要用二叉樹呢?數組

二叉樹確實在實際運用中比較少,由於有更高級的樹,可是二叉樹做爲一種最基本最典型的排序樹,是研究其餘樹的基礎。數據結構

咱們知道,在有序數組中,能夠快速找到特定的值;可是在有序數組內插入一個新數據項,或者刪除數據項,須要費時的移動全部位置改變的數據項,因此在作插入和刪除操做時,不應選用有序數組。post

另外一方面,鏈表中能夠快速添加和刪除某個數據項,可是在鏈表中查找數據不容易,必須從頭開始訪問鏈表的每個數據項,直到找到該數據爲止,過程很慢。spa

樹這種數據結構,技能像鏈表那樣快速插入和刪除,又能像有序數組那樣快速查找。這裏主要實現一種特殊的樹--二叉(搜索)樹。3d

二叉(搜索)樹有以下特色:code

1. 一個子節點的關鍵字值小於這個節點,右子節點的關鍵字值大於或者等於這個節點。blog

2. 插入一個節點須要根據這個規則進行插入。排序

3. 刪除節點時,二叉搜索樹最複雜的操做,可是刪除節點在不少樹中的應用又很是重要,因此詳細研究並總結以下特色。

  1)刪除節點要從查找要刪的節點開始入手,首先找到節點,這個要刪除的節點可能有三種狀況須要考慮:

  a. 該節點是葉節點,沒有子節點;

  b. 該節點有一個子節點;

  c. 該節點有2個子節點;

 第一種最簡單,第二種也比較簡單,第三種就至關複雜了。下面分析這三種刪除狀況:

1、要刪除葉節點,只須要改變該節點的父節點對應子字段的值便可,由指向該節點改成 null 就能夠了。垃圾回收器會自動回收葉節點,不須要本身手動刪掉。

2、當節點有一個子節點時,這個節點只有兩個鏈接:連向父節點和連向它惟一的子節點。須要從這個序列中剪斷這個節點,把它的子節點直接連到它的父節點上便可,這個過程要求改變父節點適當的引用(左子節點仍是右子節點),指向要刪除節點的子節點便可。

3、這種狀況最複雜,若是要刪除有兩個子節點的節點,就不能只用它的一個子節點代替它,好比要刪除節點25,若是用35取代它,那35的左子節點是15呢仍是30?

                                                                                                       

所以須要考慮另外一種方法,尋找它的中序後繼來代替該節點。下圖顯示的就是要刪除節點用它的後繼代替它的狀況,刪除後仍是有序的。(這裏還有更麻煩的狀況,即它的後繼本身也有右子節點,下面再討論。)

                                                                                                       

那麼如何找後繼節點呢?首先得找到要刪除的節點的右子節點,它的關鍵字值必定比待刪除節點的大。而後轉到待刪除節點右子節點的左子節點那裏(若是有的話),而後到這個左子節點的左子節點,以此類推,順着左子節點的路徑一直向下找,這個路徑上的最後一個左子節點就是待刪除節點的後繼。若是待刪除節點的右子節點沒有左子節點,那麼這個右子節點自己就是後繼。尋找後繼的示意圖以下:

                                                                                               

 

 找到了後繼節點,如今開始刪除了,先看第一種狀況,後繼節點是delNode右子節點的作後代,這種狀況要執行如下四個步驟:

  • 把後繼父節點的leftChild字段置爲後繼的右子節點;

  • 把後繼的rightChild字段置爲要刪除節點的右子節點;

  • 把待刪除節點從它父節點的leftChild或rightChild字段刪除,把這個字段置爲後繼;

  • 把待刪除的左子節點移除,將後繼的leftChild字段置爲待刪除節點的左子節點。

以下圖所示:

                                                                                         

若是後繼節點就是待刪除節點的右子節點,這種狀況就簡單了,由於只須要把後繼爲跟的子樹移到刪除的節點的位置便可。以下圖所示:

                                                                                         

看到這裏,就會發現刪除時至關棘手的操做。實際上,由於它很是複雜,一些程序員都嘗試着躲開它,他們在Node類中加了一個Boolean字段來標識該節點是否已經被刪除,在其餘操做以前會先判斷這個節點是否是已經刪除了,這樣刪除節點不會改變樹的結構。固然樹中還保留着這種已經刪除的節點,對存儲形成浪費,可是若是沒有那麼多刪除的話,這也不失爲一個好方法。

另外二叉樹有三種遍歷方式:前序、中序和後序。這個比較簡單,直接看下代碼便可。

下面手寫個二叉搜索樹的代碼

 1  public class BinaryTree {
 2       private BNode root;   //根節點
 3      public BinaryTree() {  4         root = null;  5  }  6    
 7     //二叉搜索樹查找的時間複雜度爲O(logN)
 8     public BNode find(int key) { //find node with given key
 9       BNode current = root;  10       while(current.key != key) {  11         if (key < current.key) {  12              current = current.leftChild;  13          }else {  14              current = current.rightChild;  15  }  16          if(current == null) {  17              return null;  18  }  19  }  20      return current;  21  }  22 
 23    // 插入節點
 24    public void insert(int key, double value) {  25        BNode newNode = new BNode();  26        newNode.key = key;  27        newNode.data = value;  28        if(root == null) { //if tree is null
 29             root = newNode;  30  }  31         else {  32             BNode current = root;  33  BNode parent;  34             while(true) {  35                 parent = current;  36                 if(key < current.data) { //turn left
 37                     current = current.leftChild;  38                     if(current == null) {  39                         parent.leftChild = newNode;  40                         newNode.parent = parent;  41                         return;  42  }  43                 }else { //turn right
 44                     current = current.rightChild;  45                     if(current == null) {  46                         parent.rightChild = newNode;  47                         newNode.parent = parent;  48                         return;  49  }  50  }  51  }  52  }  53  }  54  
 55   //遍歷二叉樹    
 56   public void traverse(int traverseType) {  57      switch (traverseType){  58         case 1: System.out.println("Preorder traversal:");  59  preOrder(root);//前向遍歷
 60                 break;  61         case 2: System.out.println("Inorder traversal:");  62  inOrder(root);//中向遍歷
 63                 break;  64         case 3: System.out.println("Postorder traversal:");  65  postOrder(root);//後向遍歷
 66                 break;  67         default: System.out.println("Inorder traversal:");  68  inOrder(root);  69                 break;  70  }  71         System.out.println("");  72  }  73 
 74     
 75   //前向遍歷
 76   private void preOrder(BNode localRoot) {  77       if(localRoot != null) {  78            System.out.print(localRoot.data + " ");  79    preOrder(localRoot.leftChild);  80    preOrder(localRoot.rightChild);  81    }  82   }  83    
 84   //中向遍歷
 85   private void inOrder(BNode localRoot) {  86      if(localRoot != null) {  87    inOrder(localRoot.leftChild);  88          System.out.print(localRoot.data + " ");  89    inOrder(localRoot.rightChild);  90    }  91   }  92 
 93     
 94   //後向遍歷
 95   private void postOrder(BNode localRoot) {  96      if(localRoot != null) {  97    postOrder(localRoot.leftChild);  98    postOrder(localRoot.rightChild);  99           System.out.print(localRoot.data + " "); 100    } 101   } 102 
103   //查找最小值
104     
105   /*根據二叉搜索樹的存儲規則,最小值應該是左邊那個沒有子節點的那個節點*/
106     
107   public BNode minNumber() { 108      BNode current = root; 109      BNode parent = root; 110      while(current != null) { 111           parent = current; 112           current = current.leftChild; 113    } 114       return parent; 115   } 116    
117   //查找最大值
118     
119   /*根據二叉搜索樹的存儲規則,最大值應該是右邊那個沒有子節點的那個節點*/
120     
121   public BNode maxNumber() { 122         BNode current = root; 123         BNode parent = root; 124         while(current != null) { 125             parent = current; 126             current = current.rightChild; 127  } 128         return parent; 129  } 130 
131     
132   //刪除節點
133     
134   /*
135  * 刪除節點在二叉樹中是最複雜的,主要有三種狀況: 136  * 1. 該節點沒有子節點(簡單) 137  * 2. 該節點有一個子節點(還行) 138  * 3. 該節點有兩個子節點(複雜) 139  * 刪除節點的時間複雜度爲O(logN) 140      */
141     
142   public boolean delete(int key) { 143         BNode current = root; 144 // BNode parent = root;
145         boolean isLeftChild = true; 146      if(current == null) { 147            return false; 148  } 149         //尋找要刪除的節點
150         while(current.key != key) { 151 // parent = current;
152             if(key < current.key) { 153                 isLeftChild = true; 154                 current = current.leftChild; 155  } 156             else{ 157                 isLeftChild = false; 158                 current = current.rightChild; 159  } 160             if(current == null) { 161                 return false; 162  } 163  }
164         //找到了要刪除的節點,下面開始刪除 165         //1. 要刪除的節點沒有子節點,直接將其父節點的左子節點或者右子節點賦爲null便可
166         if(current.leftChild == null && current.rightChild == null) { 167             return deleteNoChild(current, isLeftChild); 168  } 169      //3. 要刪除的節點有兩個子節點
170         else if(current.leftChild != null && current.rightChild != null) { 171             return deleteTwoChild(current, isLeftChild); 172  } 173 
174         //2. 要刪除的節點有一個子節點,直接將其砍斷,將其子節點與其父節點連起來便可,要考慮特殊狀況就是刪除根節點,由於根節點沒有父節點
175         else{ 176             return deleteOneChild(current, isLeftChild); 177  } 178 
179  } 180 
181     
182   public boolean deleteNoChild(BNode node, boolean isLeftChild) { 183         if(node == root) { 184            root = null; 185             return true; 186  } 187         if(isLeftChild) { 188             node.parent.leftChild = null; 189  } 190         else{ 191             node.parent.rightChild = null; 192  } 193         return true; 194  } 195 
196     
197   public boolean deleteOneChild(BNode node, boolean isLeftChild) { 198         if(node.leftChild == null) { 199             if(node == root) { 200                 root = node.rightChild; 201                 node.parent = null; 202                 return true; 203  } 204             if(isLeftChild) { 205                 node.parent.leftChild  = node.rightChild; 206  } 207             else{ 208                 node.parent.rightChild = node.rightChild; 209  } 210             node.rightChild.parent = node.parent; 211  } 212         else{ 213             if(node == root) { 214                 root = node.leftChild; 215                 node.parent = null; 216                 return true; 217  } 218             if(isLeftChild) { 219                 node.parent.leftChild  = node.leftChild; 220  } 221             else{ 222                 node.parent.rightChild = node.leftChild; 223  } 224             node.leftChild.parent = node.parent; 225  } 226         return true; 227  } 228 
229     
230   public boolean deleteTwoChild(BNode node, boolean isLeftChild) { 231         BNode successor = getSuccessor(node); 232         if(node == root) { 233             successor.leftChild = root.leftChild; 234             successor.rightChild = root.rightChild; 235             successor.parent = null; 236             root = successor; 237  } 238         else if(isLeftChild) { 239             node.parent.leftChild = successor; 240  } 241         else{ 242             node.parent.rightChild = successor; 243  } 244         successor.leftChild = node.leftChild;//connect successor to node's left child
245         return true; 246  } 247 
248     //得到要刪除節點的後繼節點(中序遍歷的下一個節點)
249     public BNode getSuccessor(BNode delNode) { 250         BNode successor = delNode; 251         BNode current = delNode.rightChild; 252         while(current != null) { 253             successor = current; 254             current = current.leftChild; 255  } 256         if(successor != delNode.rightChild) { 257             successor.parent.leftChild = successor.rightChild; 258             if(successor.rightChild != null) { 259                 successor.rightChild.parent = successor.parent;//刪除後續節點在原來的位置
260  } 261             successor.rightChild = delNode.rightChild;//將後續節點放到正確位置,與右邊連上
262  } 263         return successor; 264  } 265 } 266 
267 class BNode { 268     public int key; 269     public double data; 270     public BNode parent; 271     public BNode leftChild; 272     public BNode rightChild; 273 
274     public void displayNode() { 275         System.out.println("{" + key + ":" + data + "}"); 276  } 277 }         
相關文章
相關標籤/搜索