算法04 之二叉樹

在有序數組中,可以快速找到特定的值,但是想在有序數組中插入一個新的數據項,就必須首先找出新數據項插入的位置,然後將比新數據項大的數據項向後移動一位,來給新的數據項騰出空間,刪除同理,這樣移動很費時。顯而易見,如果要做很多的插入和刪除操作和刪除操作,就不該選用有序數組。

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

        樹這種數據結構,既能像鏈表那樣快速的插入和刪除,又能想有序數組那樣快速查找。這裏主要實現一種特殊的樹——二叉(搜索)樹。二叉搜索樹有如下特點:一個節點的左子節點的關鍵字值小於這個節點,右子節點的關鍵字值大於或等於這個節點。插入一個節點需要根據這個規則進行插入。

        刪除節點時二叉搜索樹中最複雜的操作,但是刪除節點在很多樹的應用中又非常重要,所以詳細研究並總結下特點。刪除節點要從查找要刪的節點開始入手,首先找到節點,這個要刪除的節點可能有三種情況需要考慮:

         ·該節點是葉節點,沒有子節點

         ·該節點有一個子節點

         ·該節點有兩個子節點

         第一種最簡單,第二種也還是比較簡單的,第三種就相當複雜了。下面分析這三種刪除情況:

        要刪除葉節點,只需要改變該節點的父節點對應子字段的值即可,由指向該節點改爲null就可以了。垃圾回收器會自動回收葉節點,不需要自己手動刪掉;當節點有一個子節點時,這個節點只有兩個連接:連向父節點和連向它唯一的子節點。需要從這個序列中剪斷這個節點,把它的子節點直接連到它的父節點上即可,這個過程要求改變父節點適當的引用(左子節點還是右子節點),指向要刪除節點的子節點即可;第三種情況最複雜,如果要刪除有兩個子節點的節點,就不能只用它的一個子節點代替它,比如要刪除節點25,如果用35取代它,那35的左子節點是15呢還是30?

   

        因此需要考慮另一種方法,尋找它的中序後繼來代替該節點。下圖顯示的就是要刪除節點用它的後繼代替它的情況,刪除後還是有序的。(這裏還有更麻煩的情況,即它的後繼自己也有右子節點,下面再討論。)


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


        找到了後繼節點,現在開始刪除了,先看第一種情況,後繼節點是delNode右子節點的做後代,這種情況要執行以下四個步驟:

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

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

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

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

        如下圖所示:


        如果後繼節點就是待刪除節點的右子節點,這種情況就簡單了,因爲只需要把後繼爲跟的子樹移到刪除的節點的位置即可。如下圖所示:


        看到這裏,就會發現刪除時相當棘手的操作。實際上,因爲它非常複雜,一些程序員都嘗試着躲開它,他們在Node類中加了一個Boolean字段來標識該節點是否已經被刪除,在其他操作之前會先判斷這個節點是不是已經刪除了,這樣刪除節點不會改變樹的結構,。當然樹中還保留着這種已經刪除的節點,對存儲造成浪費,但是如果沒有那麼多刪除的話,這也不失爲一個好方法。下面是二叉搜索樹的主要代碼:

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