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?
- class Node {
- Node leftChild;
- Node rightChild;
- int data;
- public Node(int data) {
- this.data = data;
- }
- }
有時候為了查找父節點方便,還可以為節點定義增加一個指向父節點的指針。
假設要用1-9這九個數字構建二叉樹,那麽先創建好九個節點,然後設置這些節點的左右子節點指針。觀察多個節點數不等的完全二叉樹可以得出規律,對於x個節點組成的二叉樹,只有前x / 2(取整)個節點具有子節點,且第x / 2個節點可能只有左子節點。
理解了這些後,代碼就很簡單了
[java] view plain copy print?
- import java.util.LinkedList;
- import java.util.List;
- /**
- * Created by autfish on 2016/9/13.
- */
- public class BinTreeByList {
- List<Node> nodes = null;
- private int[] datas = null;
- private int number;
- public BinTreeByList(int[] datas) {
- this.datas = datas;
- this.number = this.datas.length;
- }
- public void create() {
- nodes = new LinkedList<>();
- for(int i = 0; i < this.number; i++) {
- nodes.add(new Node(datas[i]));
- }
- //如果父節點編號為x, 那麽左子節點的編號是2x, 右子節點的編號是2x+1
- for(int noteId = 1; noteId <= this.number / 2; noteId++) {
- //索引從0開始, 需要在節點編號上減1
- nodes.get(noteId - 1).leftChild = nodes.get(noteId * 2 - 1);
- if(noteId * 2 < this.number)
- nodes.get(noteId - 1).rightChild = nodes.get(noteId * 2);
- }
- }
- private static class Node {
- Node leftChild;
- Node rightChild;
- int data;
- public Node(int data) {
- this.data = data;
- }
- }
- }
接下來的問題是,二叉樹是非線性結構,如果拿到一個已經構建好的二叉樹結構,如何遍歷其全部節點呢。遍歷的定義是按一定的規則和順序走遍二叉樹的所有節點,使每一個節點都被訪問一次,而且只被訪問一次。
先看概念:
先序遍歷(DLR):稱為先根次序遍歷,即先訪問根節點,再按先序遍歷左子樹,最後按先序遍歷右子樹。
中序遍歷(LDR):稱為中根次序遍歷,即先按中序遍歷左子樹,再訪問根節點,最後按中序遍歷右子樹。
後序遍歷(LRD):稱為後根次序遍歷,即先按後序遍歷左子樹,再按後序遍歷右子樹,最後訪問根節點。
三種方式遍歷的代碼如下:
[java] view plain copy print?
- public void preOrder(Node node) {
- if(node == null) {
- return;
- }
- System.out.print(node.data + " ");
- preOrder(node.leftChild);
- preOrder(node.rightChild);
- }
- public void inOrder(Node node) {
- if(node == null) {
- return;
- }
- inOrder(node.leftChild);
- System.out.print(node.data + " ");
- inOrder(node.rightChild);
- }
- public void postOrder(Node node) {
- if(node == null) {
- return;
- }
- postOrder(node.leftChild);
- inOrder(node.rightChild);
- System.out.print(node.data + " ");
- }
測試代碼:
[java] view plain copy print?
- public static void main(String[] args) {
- int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
- BinTreeByList tree = new BinTreeByList(numbers);
- tree.create();
- System.out.print("先序遍歷");
- tree.preOrder(tree.nodes.get(0));
- System.out.println();
- System.out.print("中序遍歷");
- tree.inOrder(tree.nodes.get(0));
- System.out.println();
- System.out.print("後續遍歷");
- tree.postOrder(tree.nodes.get(0));
- }
輸出:
[java] view plain copy print?
- 先序遍歷1 2 4 8 9 5 3 6 7
- 中序遍歷8 4 9 2 5 1 6 3 7
- 後續遍歷8 9 4 5 2 6 3 7 1
其實,完全二叉樹還有一種更簡單的存儲方式,即一維數組。也就是說int[] {1, 2, 3, 4, 5, 6, 7, 8, 9}本身就是一個完全二叉樹了。
根據數字在數組中的索引即可以計算出數字的節點位置,而且仍然可以對這個二叉樹做三種方式的遍歷。
[java] view plain copy print?
- /**
- * 完全二叉樹
- * Created by autfish on 2016/9/8.
- */
- public class BinTreeByArray {
- private int[] numbers;
- public BinTreeByArray(int[] numbers) {
- this.numbers = numbers;
- }
- /**
- * 先序遍歷
- * 根節點 -> 遍歷左子樹 -> 遍歷右子樹
- * @param nodeId
- */
- public void preOrder(int nodeId) {
- if(nodeId <= numbers.length) {
- System.out.print(numbers[nodeId - 1] + " ");
- preOrder(nodeId * 2);
- preOrder(nodeId * 2 + 1);
- }
- }
- /**
- * 中序遍歷
- * 左子樹 -> 父節點 -> 右子樹
- * @param nodeId
- */
- public void inOrder(int nodeId) {
- if(nodeId <= numbers.length) {
- inOrder(nodeId * 2);
- System.out.print(numbers[nodeId - 1] + " ");
- inOrder(nodeId * 2 + 1);
- }
- }
- /**
- * 後續遍歷
- * 左子樹 -> 右子樹 -> 父節點
- * @param nodeId
- */
- public void postOrder(int nodeId) {
- if(nodeId <= numbers.length) {
- postOrder(nodeId * 2);
- inOrder(nodeId * 2 + 1);
- System.out.print(numbers[nodeId - 1] + " ");
- }
- }
- public static void main(String[] args) {
- int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
- for(int x = 0; x < numbers.length; x++) {
- System.out.print(numbers[x] + " ");
- }
- System.out.println();
- BinTreeByArray tree = new BinTreeByArray(numbers);
- System.out.print("先序遍歷");
- tree.preOrder(1);
- System.out.println();
- System.out.print("中序遍歷");
- tree.inOrder(1);
- System.out.println();
- System.out.print("後續遍歷");
- tree.postOrder(1);
- }
- }
用數組存儲二叉樹的一個常見應用就是堆排序,下文分解。
Java與算法之(7) - 完全二叉樹