Java與算法之(7) - 完全二叉樹


 

下圖是一“棵”樹的樣子。樹這個名稱起的很形象,整個數據結構由根、枝、葉組成,其中1為根節點,2、3是1的子節點,4、5、6、8、9、10這幾個沒有子節點的節點稱為葉節點。

節點的度:一個節點的子樹的數量稱為該節點的度。例如,圖中節點2的度為3,節點3的度為2。

樹的度:一棵樹的度是指該樹中節點的最大度數。如圖中樹的度是3。

節點的層數:每個節點都處在一定的層次上,圖中根節點在第1層,2、3節點在第二層。

樹的深度:一棵樹中節點的最大層數稱為樹的深度。如中所示的樹的深度為4。

 

  • 二叉樹

 

二叉樹是一種特殊的樹,特點是每個節點最多有兩個子節點。上圖中的樹去掉節點4就符合二叉樹的定義了,如下圖:

 

  • 完全二叉樹

 

除二叉樹最后一層外,其他各層的節點數都達到最大個數,且最后一層從左向右的葉節點連續存在,只缺右側若干節點,就是完全二叉樹。

如下圖,每一層都是從左向右擺放節點,每個節點都是擺滿兩個子節點后才向右移動到下一個節點,一層擺滿后向下移動一層,直到擺放完所有數字。這樣得到的二叉樹就是完全二叉樹,中間有任何缺失的節點就不能稱為完全二叉樹。

完全二叉樹的一個重要特性就是節點編號的規律,這是理解完全二叉樹構建程序的根本。看上圖,仍然按照從左到右、從上到下的規律從1開始為節點編號,圖中節點上的數字正好與節點編號相同,可以看出:

如果一個父節點的編號是x,那么它左子節點的編號就是2x,右子節點的編號就是2x+1。

在程序中,二叉樹通常采用鏈式結構存儲,鏈中的每一個節點由節點數據、左子節點指針、右子節點指針組成

 

[java]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. class Node {  
  2.     Node leftChild;  
  3.     Node rightChild;  
  4.     int data;  
  5.   
  6.     public Node(int data) {  
  7.         this.data = data;  
  8.     }  
  9. }  

有時候為了查找父節點方便,還可以為節點定義增加一個指向父節點的指針。

 

假設要用1-9這九個數字構建二叉樹,那么先創建好九個節點,然后設置這些節點的左右子節點指針。觀察多個節點數不等的完全二叉樹可以得出規律,對於x個節點組成的二叉樹,只有前x / 2(取整)個節點具有子節點,且第x / 2個節點可能只有左子節點。

理解了這些后,代碼就很簡單了

 

[java]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. import java.util.LinkedList;  
  2. import java.util.List;  
  3.   
  4. /** 
  5.  * Created by autfish on 2016/9/13. 
  6.  */  
  7. public class BinTreeByList {  
  8.   
  9.     List<Node> nodes = null;  
  10.     private int[] datas = null;  
  11.     private int number;  
  12.   
  13.     public BinTreeByList(int[] datas) {  
  14.         this.datas = datas;  
  15.         this.number = this.datas.length;  
  16.     }  
  17.   
  18.     public void create() {  
  19.         nodes = new LinkedList<>();  
  20.         for(int i = 0; i < this.number; i++) {  
  21.             nodes.add(new Node(datas[i]));  
  22.         }  
  23.         //如果父節點編號為x, 那么左子節點的編號是2x, 右子節點的編號是2x+1  
  24.         for(int noteId = 1; noteId <= this.number / 2; noteId++) {  
  25.             //索引從0開始, 需要在節點編號上減1  
  26.             nodes.get(noteId - 1).leftChild = nodes.get(noteId * 2 - 1);  
  27.             if(noteId * 2 < this.number)  
  28.                 nodes.get(noteId - 1).rightChild = nodes.get(noteId * 2);  
  29.         }  
  30.     }  
  31.   
  32.     private static class Node {  
  33.         Node leftChild;  
  34.         Node rightChild;  
  35.         int data;  
  36.   
  37.         public Node(int data) {  
  38.             this.data = data;  
  39.         }  
  40.     }  
  41. }  

接下來的問題是,二叉樹是非線性結構,如果拿到一個已經構建好的二叉樹結構,如何遍歷其全部節點呢。遍歷的定義是按一定的規則和順序走遍二叉樹的所有節點,使每一個節點都被訪問一次,而且只被訪問一次。

 

先看概念:

先序遍歷(DLR):稱為先根次序遍歷,即先訪問根節點,再按先序遍歷左子樹,最后按先序遍歷右子樹。
中序遍歷(LDR):稱為中根次序遍歷,即先按中序遍歷左子樹,再訪問根節點,最后按中序遍歷右子樹。
后序遍歷(LRD):稱為后根次序遍歷,即先按后序遍歷左子樹,再按后序遍歷右子樹,最后訪問根節點。

三種方式遍歷的代碼如下:

 

[java]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. public void preOrder(Node node) {  
  2.      if(node == null) {  
  3.          return;  
  4.      }  
  5.      System.out.print(node.data + " ");  
  6.      preOrder(node.leftChild);  
  7.      preOrder(node.rightChild);  
  8.  }  
  9.   
  10.  public void inOrder(Node node) {  
  11.      if(node == null) {  
  12.          return;  
  13.      }  
  14.      inOrder(node.leftChild);  
  15.      System.out.print(node.data + " ");  
  16.      inOrder(node.rightChild);  
  17.  }  
  18.   
  19.  public void postOrder(Node node) {  
  20.      if(node == null) {  
  21.          return;  
  22.      }  
  23.      postOrder(node.leftChild);  
  24.      inOrder(node.rightChild);  
  25.      System.out.print(node.data + " ");  
  26.  }  

測試代碼:

 

 

[java]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. public static void main(String[] args) {  
  2.     int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };  
  3.   
  4.     BinTreeByList tree = new BinTreeByList(numbers);  
  5.     tree.create();  
  6.     System.out.print("先序遍歷");  
  7.     tree.preOrder(tree.nodes.get(0));  
  8.     System.out.println();  
  9.     System.out.print("中序遍歷");  
  10.     tree.inOrder(tree.nodes.get(0));  
  11.     System.out.println();  
  12.     System.out.print("后續遍歷");  
  13.     tree.postOrder(tree.nodes.get(0));  
  14. }  

輸出:

 

 

[java]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. 先序遍歷7   
  2. 中序遍歷7   
  3. 后續遍歷1   

其實,完全二叉樹還有一種更簡單的存儲方式,即一維數組。也就是說int[] {1, 2, 3, 4, 5, 6, 7, 8, 9}本身就是一個完全二叉樹了。

 

根據數字在數組中的索引即可以計算出數字的節點位置,而且仍然可以對這個二叉樹做三種方式的遍歷。

 

[java]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. /** 
  2.  * 完全二叉樹 
  3.  * Created by autfish on 2016/9/8. 
  4.  */  
  5. public class BinTreeByArray {  
  6.     private int[] numbers;  
  7.   
  8.     public BinTreeByArray(int[] numbers) {  
  9.         this.numbers = numbers;  
  10.     }  
  11.   
  12.     /** 
  13.      * 先序遍歷 
  14.      * 根節點 -> 遍歷左子樹 -> 遍歷右子樹 
  15.      * @param nodeId 
  16.      */  
  17.     public void preOrder(int nodeId) {  
  18.         if(nodeId <= numbers.length) {  
  19.             System.out.print(numbers[nodeId - 1] + "  ");  
  20.             preOrder(nodeId * 2);  
  21.             preOrder(nodeId * 2 + 1);  
  22.         }  
  23.     }  
  24.   
  25.     /** 
  26.      * 中序遍歷 
  27.      * 左子樹 -> 父節點 -> 右子樹 
  28.      * @param nodeId 
  29.      */  
  30.     public void inOrder(int nodeId) {  
  31.         if(nodeId <= numbers.length) {  
  32.             inOrder(nodeId * 2);  
  33.             System.out.print(numbers[nodeId - 1] + "  ");  
  34.             inOrder(nodeId * 2 + 1);  
  35.         }  
  36.     }  
  37.   
  38.     /** 
  39.      * 后續遍歷 
  40.      * 左子樹 -> 右子樹 -> 父節點 
  41.      * @param nodeId 
  42.      */  
  43.     public void postOrder(int nodeId) {  
  44.         if(nodeId <= numbers.length) {  
  45.             postOrder(nodeId * 2);  
  46.             inOrder(nodeId * 2 + 1);  
  47.             System.out.print(numbers[nodeId - 1] + "  ");  
  48.         }  
  49.     }  
  50.   
  51.     public static void main(String[] args) {  
  52.         int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };  
  53.         for(int x = 0; x < numbers.length; x++) {  
  54.             System.out.print(numbers[x] + "  ");  
  55.         }  
  56.         System.out.println();  
  57.   
  58.         BinTreeByArray tree = new BinTreeByArray(numbers);  
  59.         System.out.print("先序遍歷");  
  60.         tree.preOrder(1);  
  61.         System.out.println();  
  62.         System.out.print("中序遍歷");  
  63.         tree.inOrder(1);  
  64.         System.out.println();  
  65.         System.out.print("后續遍歷");  
  66.         tree.postOrder(1);  
  67.     }  
  68. }  

用數組存儲二叉樹的一個常見應用就是堆排序,下文分解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM