二叉樹能夠用來作什麼?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 }