1. 程式人生 > >Java與算法之(7) - 完全二叉樹

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

itl 輸出 void 結構 ray 線性 net pop pbo

下圖是一“棵”樹的樣子。樹這個名稱起的很形象,整個數據結構由根、枝、葉組成,其中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?技術分享技術分享
  1. class Node {
  2. Node leftChild;
  3. Node rightChild;
  4. int data;
  5. public Node(int data) {
  6. this.data = data;
  7. }
  8. }

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

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

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

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

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

先看概念:

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

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

[java] view plain copy print?技術分享技術分享
  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. public void inOrder(Node node) {
  10. if(node == null) {
  11. return;
  12. }
  13. inOrder(node.leftChild);
  14. System.out.print(node.data + " ");
  15. inOrder(node.rightChild);
  16. }
  17. public void postOrder(Node node) {
  18. if(node == null) {
  19. return;
  20. }
  21. postOrder(node.leftChild);
  22. inOrder(node.rightChild);
  23. System.out.print(node.data + " ");
  24. }

測試代碼:

[java] view plain copy print?技術分享技術分享
  1. public static void main(String[] args) {
  2. int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  3. BinTreeByList tree = new BinTreeByList(numbers);
  4. tree.create();
  5. System.out.print("先序遍歷");
  6. tree.preOrder(tree.nodes.get(0));
  7. System.out.println();
  8. System.out.print("中序遍歷");
  9. tree.inOrder(tree.nodes.get(0));
  10. System.out.println();
  11. System.out.print("後續遍歷");
  12. tree.postOrder(tree.nodes.get(0));
  13. }

輸出:

[java] view plain copy print?技術分享技術分享
  1. 先序遍歷1 2 4 8 9 5 3 6 7
  2. 中序遍歷8 4 9 2 5 1 6 3 7
  3. 後續遍歷8 9 4 5 2 6 3 7 1

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

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

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

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

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