跟左神學算法6 進階數據結構(二叉樹)

內容:node

一、二叉樹基礎結構及經常使用函數數組

二、三種二叉樹app

三、二叉樹的後繼節點與前繼節點函數

四、二叉樹的序列化和反序列化this

五、摺紙問題spa

六、求樹的節點指針

 

 

 

一、二叉樹基礎結構及經常使用函數code

二叉樹的基本結構:blog

1 public static class Node {
2     public int value;
3     public Node left;
4     public Node right;
5             
6     public Node(int data) {
7         this.value = data;
8     }
9 }

 

二叉樹經常使用函數:遞歸

遍歷二叉樹:

  • 遞歸先序遍歷、遞歸中序遍歷、遞歸後序遍歷
  • 非遞歸先序遍歷、非遞歸中序遍歷、非遞歸後序遍歷
  • 非遞歸按層遍歷

代碼:

  1 // 遞歸先序遍歷
  2 public static void preOrderRecur(Node head){
  3     if(head == null){
  4         return;
  5     }    
  6     System.out.print(head.value + " ");
  7     preOrderRecur(head.left);
  8     preOrderRecur(head.right);
  9 }
 10         
 11 // 遞歸中序遍歷
 12 public static void inOrderRecur(Node head){
 13     if(head == null){
 14         return;
 15     }    
 16     inOrderRecur(head.left);
 17     System.out.print(head.value + " ");
 18     inOrderRecur(head.right);        
 19 }
 20         
 21 // 遞歸後序遍歷
 22 public static void posOrderRecur(Node head){
 23     if(head == null){
 24         return;
 25     }    
 26     posOrderRecur(head.left);
 27     posOrderRecur(head.right);
 28     System.out.print(head.value + " ");            
 29 }
 30                     
 31 // 非遞歸先序遍歷
 32 public static void preOrderUnRecur(Node head){
 33     System.out.print("pre-order: ");
 34     if(head != null){
 35         Stack<Node> stack = new Stack<Node>();
 36         stack.add(head);
 37         while(!stack.isEmpty()){
 38             head = stack.pop();
 39             System.out.print(head.value + " ");
 40             if(head.right != null){
 41                 stack.push(head.right);
 42             }
 43             if(head.left != null){
 44                 stack.push(head.left);
 45             }
 46         }
 47     }        
 48 }    
 49         
 50 // 非遞歸中序遍歷
 51 public static void inOrderUnRecur(Node head){
 52     System.out.print("in-order: ");
 53     if(head != null){
 54         Stack<Node> stack = new Stack<Node>();
 55         while(!stack.isEmpty() || head != null){
 56             if(head != null){
 57                 stack.push(head);
 58                 head = head.left;
 59             } else{
 60                 head = stack.pop();
 61                 System.out.print(head.value + " ");
 62                 head = head.right;
 63             }
 64         }
 65     }
 66 }
 67         
 68 // 非遞歸後序遍歷
 69 public static void posOrderUnRecur(Node head){
 70     System.out.print("pos-order: ");
 71     if(head != null){
 72         Stack<Node> s1 = new Stack<Node>();
 73         Stack<Node> s2 = new Stack<Node>();        // 輔助棧
 74         s1.push(head);
 75         while(!s1.isEmpty()){
 76             head = s1.pop();
 77             s2.push(head);
 78             if(head.left != null){
 79                 s1.push(head.left);
 80             }
 81             if(head.right != null){
 82                 s1.push(head.right);
 83             }
 84         }
 85         
 86         while(!s2.isEmpty()){
 87             head = s2.pop();
 88             System.out.print(head.value + " ");
 89         }
 90     }
 91 }    
 92         
 93 // 非遞歸按層遍歷 -> 隊列實現
 94 public static void levelOrderUnRecur(Node head) {
 95     System.out.print("level-order: ");
 96     if (head != null) {
 97         Queue<Node> que = new LinkedList<Node>();
 98         que.offer(head);
 99         while (!que.isEmpty()) {
100             head = que.poll();
101             System.out.print(head.value + " ");
102             if (head.left != null) {
103                 que.offer(head.left);
104             }
105             if (head.right != null) {
106                 que.offer(head.right);
107             }
108         }
109     }
110     System.out.println();
111 }

 

打印二叉樹:

下面是一個打印二叉樹的福利函數,使用方法: 打印節點標誌爲H表示頭節點 打印節點標誌爲v表示爲右子樹 打印節點標誌爲^表示爲左子樹

 1 // 打印二叉樹的入口函數
 2 public static void printTree(Node head) {
 3     System.out.println("Binary Tree:");
 4     printInOrder(head, 0, "H", 17);
 5     System.out.println();
 6 }
 7         
 8 // 打印二叉樹的主邏輯函數
 9 public static void printInOrder(Node head, int height, String to, int len) {
10     /*
11      * head 頭節點
12      * height 當前高度
13      * to 表明是左子樹仍是右子樹 => 爲H時表明根節點
14      * len 表明每一個節點最多佔的寬度
15      * */
16     if (head == null) {
17         return;
18     }
19     
20     // 繼續打印右子樹
21     printInOrder(head.right, height + 1, "v", len);
22     
23     String val = to + head.value + to;
24     int lenM = val.length();            // 節點(加上節點標誌)的長度
25     int lenL = (len - lenM) / 2;        // 節點左邊的長度
26     int lenR = len - lenM - lenL;        // 節點右邊的長度
27     val = getSpace(lenL) + val + getSpace(lenR);        // 爲節點加上左右兩邊的空格
28     System.out.println(getSpace(height * len) + val);    // 打印當前節點
29     
30     // 繼續打印左子樹
31     printInOrder(head.left, height + 1, "^", len);
32 }
33         
34 // 爲節點加上左右兩邊的空格
35 public static String getSpace(int num) {
36     String space = " ";
37     StringBuffer buf = new StringBuffer("");
38     for (int i = 0; i < num; i++) {
39         buf.append(space);
40     }
41     return buf.toString();
42 }

 

 

二、三種二叉樹

  • 平衡二叉樹:空樹 或 二叉樹的每一個左右子樹的高度差的絕對值不超過1
  • 搜索二叉樹:空樹 或 左子樹上全部結點的值均小於它的根結點的值以及右子樹上全部結點的值均大於它的根結點的值
  • 徹底二叉樹:空樹 或 對於深度爲K的,有n個結點的二叉樹,當且僅當其每個結點都與深度爲K的滿二叉樹中編號從1至n的結點一一對應時稱之爲徹底二叉樹

判斷是不是平衡二叉樹:

 1 // 返回數據
 2 public static class ReturnData{
 3     public boolean isB;
 4     public int h;
 5     
 6     public ReturnData(boolean isB, int h){
 7         this.isB = isB;
 8         this.h = h;
 9     }
10 }
11 
12 // 判斷一顆二叉樹是不是平衡二叉樹
13 public static boolean isB(Node head){
14     return process(head).isB;
15 }
16 
17 // 遞歸求解返回數據過程 => 列舉多種可能性
18 // 不是平衡二叉樹的狀況: 一、左子樹不是平衡二叉樹  二、右子樹不是平衡二叉樹  三、左子樹和右子樹的高度差大於1
19 // 其餘狀況均是平衡二叉樹: 空二叉樹(高度爲0)、不是以上三種狀況的非空二叉樹(高度爲左右子樹最大高度+1)
20 public static ReturnData process(Node head){
21     if(head==null){
22         return new ReturnData(true, 0);
23     }
24     ReturnData leftData = process(head.left);
25     if(!leftData.isB){
26         return new ReturnData(false, 0);
27     }
28     ReturnData rightData = process(head.right);
29     if(!rightData.isB){
30         return new ReturnData(false, 0);
31     }
32     if(Math.abs(leftData.h - rightData.h) > 1){
33         return new ReturnData(false, 0);
34     }
35     return new ReturnData(true, Math.max(leftData.h, rightData.h) + 1);
36 }

 

判斷是不是搜索二叉樹:

 1 // 藉助非遞歸中序遍歷實現(最簡單的方法) -> 後一個節點必須大於前一個節點
 2 public static boolean isBST(Node head) {
 3     int pre = Integer.MIN_VALUE;
 4     if (head != null) {
 5         Stack<Node> stack = new Stack<Node>();
 6         while(!stack.isEmpty() || head!=null){
 7             if(head != null){
 8                 stack.push(head);
 9                 head = head.left;
10             } else{
11                 head = stack.pop();
12                 if(head.value > pre){
13                     pre = head.value;
14                 } else{
15                     return false;
16                 }
17                 head = head.right;
18             }
19         }
20     }
21     
22     return true;
23 }
24         
25 // 藉助遞歸中序遍歷實現 判斷一棵二叉樹是不是搜索二叉樹
26 private static int pre = Integer.MIN_VALUE;
27 private static boolean flag;
28 public static void isBST2(Node head) {
29     if (head == null) {
30         return;
31     }
32     isBST2(head.left);
33     // 將當前節點和以前的數比較 若是大於說明知足搜索二叉樹條件 不然不知足
34     if (head.value > pre) {
35         pre = head.value;
36     } else {
37         flag = false;
38         return;
39     }
40     isBST2(head.right);
41 }

 

判斷是不是徹底二叉樹:

 1 // 按層遍歷 兩種狀況: 
 2 // 一、有右節點無左節點 直接返回false
 3 // 二、有左節點無右節點或者左右節點都沒有 那麼若是接下來的節點都是葉節點就直接返回true 不然返回false
 4 
 5 public static boolean isCBT(Node head) {
 6     if (head == null) {
 7         return true;
 8     }
 9     Queue<Node> queue = new LinkedList<Node>();
10     boolean leaf = false;        // 標誌狀況2是否發生
11     Node l = null;
12     Node r = null;
13     queue.offer(head);
14     while (!queue.isEmpty()) {
15         head = queue.poll();
16         l = head.left;
17         r = head.right;
18         if (l == null && r != null) {
19             // 狀況1 左空右不空
20             return false;
21         }
22         if (leaf && (l != null || r != null)) {
23             // 狀況2
24             return false;
25         }
26         if (l != null) {
27             queue.offer(l);
28         }
29         if (r != null) {
30             queue.offer(r);
31         } else {
32             // 開啓狀況2
33             // 實質上就是左右均爲空或左不空右空的狀況
34             leaf = true;
35         }
36     }
37     return true;
38 }

 

 

三、二叉樹的後繼節點與前繼節點

題目描述:

如今有一種新的二叉樹節點類型以下:
public class Node {
  public int value;
  public Node left;
  public Node right;
  public Node parent;

  public Node(int data)
  {
    this.value = data;
  }
}
該結構比普通二叉樹節點結構多了一個指向父節點的parent指針。假設有一棵Node類型的節點組成的
二叉樹,樹中每一個節點的parent指針都正確地指向本身的父節點,頭節點的parent指向null
只給一個在二叉樹中的某個節點node,請實現返回node的後繼節點的函數。在二叉樹的中序遍歷的序列中,node的下一個節點叫做node的後繼節點

另外前繼節點就是中序遍歷中當前節點的前一個節點,方法和找後繼節點相似

 

思路:

根據這個節點有無右子樹來討論:

  • 若是節點x有右子樹 那麼x的後繼節點必定是它右子樹中最左的節點
  • 若是節點x沒有右子樹 經過parent指針一直向上找父節點 若是當前節點是父節點的左孩子那麼節點x的後繼節點是當前節點的父節點
  • 另外也要注意最後一個節點的問題(經過判斷父節點爲空解決)

 

代碼(後繼節點):

 1 // 找到節點的後繼節點
 2 public static Node getSuccessorNode(Node node) {
 3     if (node == null) {
 4         return node;
 5     }
 6     if (node.right != null) {
 7         // 節點有右子樹
 8         // 找到右子樹上最左的節點
 9         return getLeftMost(node.right);
10     } else {
11         // 節點沒有右子樹
12         Node parent = node.parent;
13         while (parent != null && parent.left !=  node) {
14             node = parent;
15             parent = node.parent;
16         }
17         return parent;
18     }
19 }
20         
21 // 找到右子樹上最左的節點
22 public static Node getLeftMost(Node node) {
23     while (node.left != null) {
24         node = node.left;
25     }
26         
27     return node;
28 }

 

代碼(前繼節點):

 1 // 找到節點的前繼節點
 2 public static Node getPredecessorNode(Node head) {
 3     if (head == null) {
 4         return null;
 5     }
 6 
 7     if (head.left != null) {
 8         return getRightMost(head.left);
 9     } else {
10         Node parent = head.parent;
11         while(parent!=null&&parent.right!=head){
12             head = parent;
13             parent = head.parent;
14         }
15             
16         return parent;
17     }
18 }
19 
20 // 得到左子樹中最右的節點
21 public static Node getRightMost(Node head) {
22      while (head.right != null) {
23          head = head.right;
24      }
25 
26      return head;
27 }

 

 

四、二叉樹的序列化和反序列化

二叉樹的序列化: 二叉樹的序列化是指把一棵二叉樹按照某種遍歷方式的結果以某種格式保存爲字符串,
從而使得內存中創建起來的二叉樹能夠持久保存。序列化能夠基於先序、中序、後序、按層的遍歷方式進行
序列化的結果是一個字符串,經過#表示空節點, 以!表示一個結點值的結束, 節點之間用下劃線分割


二叉樹的反序列化: 根據某種遍歷順序獲得的序列化字符串結果str,重構二叉樹

 

代碼:

 1 // 下面是之前序遍歷和按層遍歷實現的序列化和反序列化
 2 // 以先序遍歷的方式序列化二叉樹
 3 public static String serialByPre(Node head) {
 4     if (head == null) {
 5         return "#_";
 6     }
 7     String res = head.value + "_";
 8     res += serialByPre(head.left);
 9     res += serialByPre(head.right);
10     return res;
11 }
12 
13 // 以按層遍歷的方式序列化
14 public static String serialByLevel(Node head) {
15     if (head == null) {
16         return "#_";
17     }
18     String res = head.value + "_";
19     Queue<Node> queue = new LinkedList<Node>();
20     queue.offer(head);
21     while (!queue.isEmpty()) {
22         head = queue.poll();
23         // 處理左邊
24         if (head.left != null) {
25             res += head.left.value + "_";
26             queue.offer(head.left);
27         } else {
28             res += "#_";
29         }
30         // 處理右邊
31         if (head.right != null) {
32             res += head.right.value + "_";
33             queue.offer(head.right);
34         } else {
35             res += "#_";
36         }
37     }
38     return res;
39 }
40 
41 // 實現先序遍歷反序列化二叉樹
42 public static Node reconPreOrder(Queue<String> queue) {
43     String value = queue.poll();
44     if (value.equals("#")) {
45         return null;
46     }
47     Node head = new Node(Integer.valueOf(value));
48     head.left = reconPreOrder(queue);
49     head.right = reconPreOrder(queue);
50     return head;
51 }
52 
53 // 將字符串分割轉換成數組而後存入隊列中而後調用反序列化函數
54 public static Node reconByString(String str) {
55     String[] values = str.split("_");
56     // 保存分割出來的節點值
57     Queue<String> queue = new LinkedList<String>();
58     for (int i = 0; i != values.length; i++) {
59         queue.offer(values[i]);
60     }
61         
62     return reconPreOrder(queue);
63 }
64 
65 // 根據節點值生成節點
66 public static Node generateNodeByString(String val) {
67     if (val.equals("#")) {
68         return null;
69     }
70     return new Node(Integer.valueOf(val));
71 }
72 
73 // 實現按層遍歷反序列化二叉樹
74 public static Node reconByLevelString(String levelStr) {
75     String[] values = levelStr.split("_");
76     int index = 0;
77     Node head = generateNodeByString(values[index++]);
78     Queue<Node> queue = new LinkedList<Node>();
79     if (head != null) {
80         queue.offer(head);
81     }
82     Node node = null;
83     while (!queue.isEmpty()) {
84         node = queue.poll();
85         node.left = generateNodeByString(values[index++]);
86         node.right = generateNodeByString(values[index++]);
87         if (node.left != null) {
88             queue.offer(node.left);
89         }
90         if (node.right != null) {
91             queue.offer(node.right);
92         }
93     }
94     return head;
95 }

 

 

五、摺紙問題

題目描述:

請把一段紙條豎着放在桌子上,而後從紙條的下邊向 上方對摺1次,壓出摺痕後展開。
此時 摺痕是凹下去的,即摺痕 突起的方向指向紙條的背面。若是從紙條的下邊向上方連續對2
次,壓出摺痕後展開,此時有三條摺痕,從上到下依次是下摺痕、下摺痕和上摺痕。 給定一個
輸入參數N,表明紙條都從下邊向上方連續對摺N次, 請從上到下打印全部摺痕的方向。
例如:N=1時,打印: down N=2時,打印: down down up

 

思路:

其實對於紙折的次數與樹有關係,也就是說能夠使用樹中的便利方法來求解:
對摺次數:

  • 1  down
  • 2  down down up
  • 3  down down up down down up up 
  • 4  down down up down down up up down down down up up down up up 

題目中要求是從上到下的摺痕的數組,那麼就能夠使用二叉樹的中序遍歷序列來列出全部的從上到下的數組

代碼:

 1 public static void printProcess(int i, int N, boolean down) {
 2     if (i > N) {
 3         return;
 4     }
 5     // 實質上就是中序遍歷二叉樹
 6     printProcess(i + 1, N, true);
 7     System.out.print(down ? "down " : "up ");
 8     printProcess(i + 1, N, false);
 9 }
10 
11 // 打印全部摺痕(由上到下)
12 public static void printAllFolds(int N) {
13     printProcess(1, N, true);
14 }
15     
16 public static void main(String[] args) {
17     int N = 4;
18     printAllFolds(N);
19     System.out.println();
20 }

 

 

六、求樹的節點

題目描述:

已知一棵徹底二叉樹,求其節點的個數
要求: 時間複雜度低於O(N),N爲這棵樹的節點個數

 

思路:

採用二分的思想。看徹底二叉樹的最後的最右一個節點的位置
一、找到徹底二叉樹的最左節點,也就是求左子樹的深度
二、找到徹底二叉樹頭節點右子樹中的最左節點,記錄右子樹深度
三、若是兩個深度相等,說明頭節點左子樹是一棵滿二叉樹,使用公式求得左子樹長度再加上頭節點,而後對於右子樹使用遞歸求解
四、若是左子樹深度大於右子樹深度,說明右子樹是一棵徹底二叉樹,使用公式求得右子樹長度再加上頭節點,而後對於左子樹使用遞歸求解

 

代碼:

 1 // 主邏輯函數
 2 public static int nodeNum(Node head) {
 3     if (head == null) {
 4         return 0;
 5     }
 6     return bs(head, 1, mostLeftLevel(head, 1));
 7 }
 8 
 9 // 遞歸過程
10 public static int bs(Node node, int level, int h) {
11     // node: 當前節點        level: 當前節點位於第幾層        h:  樹的層數
12     if (level == h) {
13         return 1;
14     }
15     if (mostLeftLevel(node.right, level + 1) == h) {
16         // 當前節點的右子樹能夠到最後一層
17         // 此時左子樹滿
18         return (1 << (h - level)) + bs(node.right, level + 1, h);
19     } else {
20         // 當前節點的右子樹不能夠到最後一層
21         // 此時右子樹滿
22         return (1 << (h - level - 1)) + bs(node.left, level + 1, h);
23     }
24 }
25     
26 // 返回當前節點的最左子節點位於的層數
27 public static int mostLeftLevel(Node node, int level) {
28     while (node != null) {
29         level++;
30         node = node.left;
31     }
32     return level - 1;
33 }
相關文章
相關標籤/搜索