Java 樹結構的基礎部分(二)
阿新 • • 發佈:2021-03-11
1 順序儲存二叉樹
1.1 順序儲存二叉樹的概念
基本說明
從資料儲存來看,陣列儲存方式和樹的儲存方式可以相互轉換,即陣列可以轉換成樹,樹也可以轉換成陣列,
看下面的示意圖。
要求:
1) 右圖的二叉樹的結點,要求以陣列的方式來存放 arr : [1, 2, 3, 4, 5, 6, 6]
2) 要求在遍歷陣列 arr 時,仍然可以以前序遍歷,中序遍歷和後序遍歷的方式完成結點的遍歷
順序儲存二叉樹的特點:
1) 順序二叉樹通常只考慮完全二叉樹
2) 第 n 個元素的左子節點為 2 * n + 1
3) 第 n 個元素的右子節點為 2 * n + 2
4) 第 n 個元素的父節點為 (n-1) / 2
5) n : 表示二叉樹中的第幾個元素(按 0 開始編號如圖所示)
1.2 順序儲存二叉樹遍歷
需求: 給你一個數組 {1,2,3,4,5,6,7},要求以二叉樹前序遍歷的方式進行遍歷。 前序遍歷的結果應當為
1,2,4,5,3,6,7
程式碼實現
說明: 當線索化二叉樹後,Node 節點的 屬性 left 和 right ,有如下情況:
1) left 指向的是左子樹,也可能是指向的前驅節點. 比如 ① 節點 left 指向的左子樹, 而 ⑩ 節點的 left 指向的
就是前驅節點.
2) right 指向的是右子樹,也可能是指向後繼節點,比如 ① 節點 right 指向的是右子樹,而⑩ 節點的 right 指向
的是後繼節點.
程式碼實現:
2.4 遍歷線索化二叉樹
1) 說明:對前面的中序線索化的二叉樹, 進行遍歷
2) 分析:因為線索化後,各個結點指向有變化,因此原來的遍歷方式不能使用,這時需要使用新的方式遍歷
線索化二叉樹,各個節點可以通過線型方式遍歷,因此無需使用遞迴方式,這樣也提高了遍歷的效率。 遍歷的次
序應當和中序遍歷保持一致。
3) 程式碼:
如上面
package com.lin.tree_0308; public class ArrBinaryTreeDemo { public static void main(String[] args) { int[] arr = {1,2,3,4,5,6,7}; ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr); System.out.println("前序遍歷:"); arrBinaryTree.preOrder(); System.out.println(); System.out.println("中序遍歷:"); arrBinaryTree.infixOrder(); System.out.println(); System.out.println("後序遍歷:"); arrBinaryTree.postOrder(); } } class ArrBinaryTree{ private int[] arr; public ArrBinaryTree(int[] arr) { super(); this.arr = arr; } // 過載 public void preOrder() { this.preOrder(0); } public void infixOrder() { this.infixOrder(0); } public void postOrder() { this.postOrder(0); } /** * * @Description: * @author LinZM * @date 2021-3-8 19:14:45 * @version V1.8 * @param index 陣列下標 */ // 前序遍歷 public void preOrder(int index) { if(arr == null || arr.length == 0) { System.out.println("陣列為空!"); } // 輸出當前資料 System.out.print(arr[index] + " "); // 向左遞迴 if( ( index * 2 + 1 ) < arr.length ) { preOrder( index * 2 + 1 ); } // 向右遞迴 if( ( index * 2 +2 ) < arr.length ) { preOrder( index * 2 + 2 ); } } // 中序遍歷 public void infixOrder(int index) { if(arr == null || arr.length == 0) { System.out.println("陣列為空!"); } // 向左遞迴 if( ( index * 2 + 1 ) < arr.length) { infixOrder( index * 2 + 1); } // 輸出當前資料 System.out.print(arr[index] + " "); // 向右遞迴 if( ( index * 2 + 2 ) < arr.length) { infixOrder(index*2 + 2); } } // 後序遍歷 public void postOrder(int index) { if(arr == null || arr.length == 0) { System.out.println("陣列為空!"); } // 向左遞迴 if( ( index * 2 + 1) < arr.length ) { postOrder(index * 2 + 1); } // 向右遞迴 if( ( index * 2 + 2 ) < arr.length) { postOrder(index * 2 + 2); } // 輸出當前資料 System.out.print(arr[index] + " "); } }
2 線索化二叉樹 2.1 先看一個問題 將數列 {1, 3, 6, 8, 10, 14 } 構建成一顆二叉樹. n+1=7 問題分析: 1) 當我們對上面的二叉樹進行中序遍歷時,數列為 {8, 3, 10, 1, 6, 14 } 2) 但是 6, 8, 10, 14 這幾個節點的 左右指標,並沒有完全的利用上. 3) 如果我們希望充分的利用 各個節點的左右指標, 讓各個節點可以指向自己的前後節點,怎麼辦? 4) 解決方案-線索二叉樹 2.2 線索二叉樹基本介紹 1) n 個結點的二叉連結串列中含有 n+1 【公式 2n-(n-1)=n+1】 個空指標域。利用二叉連結串列中的空指標域,存放指向 該結點在某種遍歷次序下的前驅和後繼結點的指標(這種附加的指標稱為"線索") 2) 這種加上了線索的二叉連結串列稱為線索連結串列,相應的二叉樹稱為線索二叉樹(Threaded BinaryTree)。根據線索性質 的不同,線索二叉樹可分為前序線索二叉樹、中序線索二叉樹和後序線索二叉樹三種 3) 一個結點的前一個結點,稱為前驅結點 4) 一個結點的後一個結點,稱為後繼結點 2.3 線索二叉樹應用案例 應用案例說明:將下面的二叉樹,進行中序線索二叉樹。中序遍歷的數列為 {8, 3, 10, 1, 14, 6} 思路分析: 中序遍歷的結果:{8, 3, 10, 1, 14, 6}
package com.lin.tree_0308; public class ThreadeBinaryTreeDemo { public static void main(String[] args) { TNode tNode1 = new TNode(1, "Tom"); TNode tNode2 = new TNode(3, "Jack"); TNode tNode3 = new TNode(6, "Smith"); TNode tNode4 = new TNode(8, "Marry"); TNode tNode5 = new TNode(10, "Linda"); TNode tNode6 = new TNode(14, "King"); tNode1.setLeft(tNode2); tNode1.setRight(tNode3); tNode2.setLeft(tNode4); tNode2.setRight(tNode5); tNode3.setLeft(tNode6); TBinaryTree tBinaryTree = new TBinaryTree(); tBinaryTree.setRoot(tNode1); tBinaryTree.threadedInfixNodes(tNode1);// 10test TNode left = tNode5.getLeft(); TNode right = tNode5.getRight(); System.out.println(left); System.out.println(right); // // 中序遍歷線索化二叉樹 // System.out.println("中序遍歷線索化二叉樹:"); // tBinaryTree.threadedInfixList(); } } class TBinaryTree{ private TNode root; // 前驅節點的指標,總是保留前一個節點 private TNode pre; public void setRoot(TNode root) { this.root = root; } public void threadedPreNodes(TNode node) { if(node == null) { System.out.println("空!!!"); return; } // 1 線索當前節點 // 先處理節點的前驅節點 if (node.getLeft() == null) { // 讓當前節點的左指標指向前驅節點 node.setLeft(pre); // 修改當前節點的左指標的型別 node.setLeftType(1); } // 處理後繼節點 if (pre != null && pre.getRight() == null) { pre.setRight(node); pre.setRigthType(1); } // 每處理一個節點後,讓當前節點是下一個節點前驅節點 pre = node; threadedPreNodes(node.getLeft()); threadedPreNodes(node.getRight()); } // 二叉樹中序線索化 /** * * @Description: * @author LinZM * @date 2021-3-8 22:14:51 * @version V1.8 * @param node 線索化節點 */ public void threadedInfixNodes(TNode node) { if(node == null) { return; } // 1 先線索化左子樹 threadedInfixNodes(node.getLeft()); // 2 線索化當前節點 // 先處理節點的前驅節點 // 節點8->節點3,一開始8為node,後面3為node if(node.getLeft() == null ) { // 讓當前節點的左指標指向前驅節點 node.setLeft(pre); // 修改當前節點的左指標的型別 node.setLeftType(1); } // 處理後繼節點 if(pre != null && pre.getRight() == null) { pre.setRight(node); pre.setRigthType(1); } // 每處理一個節點後,讓當前節點是下一個節點前驅節點 // pre = 8 pre = node; // 3 線索化右子樹 threadedInfixNodes(node.getRight()); } // 中序遍歷線索二叉樹 public void threadedInfixList() { // 定義一個變數,儲存當前遍歷的節點,從root開始 TNode node = root; if(node == null) { System.out.println("空樹!"); return; } while(node != null) { // 迴圈找到leftType == 1 的節點,第一個找到就是8節點 // 後面隨著遍歷而變化,因為當leftType == 1,說明該節點是按照線索化處理後的有效節點 while(node.getLeftType() == 0) { node = node.getLeft(); } // 找到8節點 System.out.println(node); // 如果當前節點的右指標指向的是後繼節點,就一直輸出 while(node.getRigthType() == 1) { node = node.getRight(); System.out.println(node); } // 如果不是後繼節點,則替換這個遍歷的節點 node = node.getRight(); } } // 刪除節點 public void delNode(int no) { if (root != null) { // 如果只有一個root if (root.getNo() == no) { root = null; } else { root.delNode(no); } } else { System.out.println("空樹!"); } } // 前序遍歷 public void preOrder() { if(this.root != null) { this.root.preOrder(); } else { System.out.println("二叉樹為空!"); } } // 中序遍歷 public void infixOrder() { if(this.root != null) { this.root.infixOrder(); } else { System.out.println("二叉樹為空!"); } } // 後序遍歷 public void postOrder() { if(this.root != null) { this.root.postOrder(); } else { System.out.println("二叉樹為空!"); } } // 前序查詢 public TNode preOrderSearch(int no) { if(root != null) { return root.preOrderSearch(no); } else { return null; } } // 中序查詢 public TNode infixOrderSearch(int no) { if (root != null) { return root.infixOrderSearch(no); } else { return null; } } // 後序查詢 public TNode postOrderSearch(int no) { if (root != null) { return root.postOrderSearch(no); } else { return null; } } } class TNode{ private String name; private int no; private TNode left; private TNode right; // leftType == 0 為左子樹, 如果為1則表示指向前驅節點 // rightType == 0 為右子樹, 如果為1則表示指向後繼節點 private int leftType; private int rigthType; public int getLeftType() { return leftType; } public void setLeftType(int leftType) { this.leftType = leftType; } public int getRigthType() { return rigthType; } public void setRigthType(int rigthType) { this.rigthType = rigthType; } public TNode(int no, String name) { this.no = no; this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getNo() { return no; } public void setNo(int no) { this.no = no; } public TNode getLeft() { return left; } public void setLeft(TNode left) { this.left = left; } public TNode getRight() { return right; } public void setRight(TNode right) { this.right = right; } @Override public String toString() { return "TNode [name=" + name + ", no=" + no + "]"; } // 前序遍歷 public void preOrder() { System.out.println(this); // 輸出父節點 if(this.left != null) { this.left.preOrder(); } if(this.right != null) { this.right.preOrder(); } } // 中序遍歷 public void infixOrder() { if (this.left != null) { this.left.infixOrder(); } System.out.println(this); // 輸出父節點 if (this.right != null) { this.right.infixOrder(); } } // 前序遍歷 public void postOrder() { if (this.left != null) { this.left.postOrder(); } if (this.right != null) { this.right.postOrder(); } System.out.println(this); // 輸出父節點 } // 前序查詢 public TNode preOrderSearch(int no) { System.out.println("1"); // 比較當前節點是不是 if(this.no == no) { return this; } // 1 判斷當前節點的左節點是否為空,如果不為空,則遞迴前序查詢 // 2 如果左遞迴前序查詢,找到節點,則返回 TNode resNode = null; if(this.left != null) { resNode = this.left.preOrderSearch(no); } if(resNode != null) {// 說明左子樹找到了 return resNode; } // 1 左遞迴如果沒有找到,則繼續判斷 // 2 當前節點的右節點是否為空,如果不為空,則繼續向右遞迴前序查詢 if(this.right != null) { resNode = this.right.preOrderSearch(no); } // 這時候不管有沒有找到都要返回resNode return resNode; } // 中序查詢 public TNode infixOrderSearch(int no) { TNode resNode = null; if(this.left != null) { resNode = this.left.infixOrderSearch(no); } if(resNode != null) { return resNode; } System.out.println("1"); if(this.no == no) { return this; } if(this.right != null) { resNode = this.right.infixOrderSearch(no); } return resNode; } // 後序查詢 public TNode postOrderSearch(int no) { TNode resNode = null; if(this.left != null) { resNode = this.left.postOrderSearch(no); } if(resNode != null) { return resNode; } if(this.right != null) { resNode = this.right.postOrderSearch(no); } if(resNode != null) { return resNode; } System.out.println("1"); if(this.no == no) { return this; } // 如果都沒有找到 return resNode; } /** * * @Description:1 因為我們的二叉樹是單向,所以我們是判斷當前節點的子節點是否需要刪除節點,而不是直接去判斷當前節點是否需要刪除節點。<br> * 2 如果當前節點的左子節點不為空,並且左子節點就是要刪除節點,就將this.left = null;並且就返回(結束遞迴刪除) <br> * 3 如果當前節點的右子節點不為空,並且右子節點就是要刪除節點,就將this.right = null;並且就返回(結束遞迴刪除) <br> * 4 如果第2和第3都沒有刪除節點,那麼我們就需要向左子樹進行遞迴刪除<br> * 5 如果第4補也沒有刪除節點,則向右子樹進行遞迴刪除<br> * @author LinZM * @date 2021-3-8 15:17:32 * @version V1.8 */ public void delNode(int no) { if(this.left != null && this.left.no == no) { this.left = null; return; } if(this.right != null && this.right.no == no) { this.right = null; return; } if(this.left != null) { this.left.delNode(no); } if(this.right != null) { this.right.delNode(no); } } }
僅供參考,有錯誤還請指出!
有什麼想法,評論區留言,互相指教指教。
覺得不錯的可以點一下右邊的推薦喲