學習筆記——二叉樹相關演算法的實現(Java語言版)
二叉樹遍歷概念和演算法
遍歷(Traverse):
所謂遍歷(Traversal)是指沿著某條搜尋路線,依次對樹中每個結點均做一次且僅做一次訪問。
從二叉樹的 遞迴 定義可知,一棵非空的二叉樹由根結點及左、右子樹這三個基本部分組成。
因此,在任一給定結點上,可以按某種次序執行三個操作:
⑴ 訪問結點本身(D),
⑵ 遍歷該結點的左子樹(L),
⑶ 遍歷該結點的右子樹(R)。
先序/根遍歷DLR:根 左子樹 右子樹
中序/根遍歷LDR:左子樹 根 右子樹
後根/序遍歷LRD:左子樹 右子樹 根
注意:由於樹的遞迴定義,其實對三種遍歷的概念其實也是一個遞迴的描述過程
演算法實現:
二叉樹結點類
/** * 二叉連結串列的結點 * @author shangyang * */ public class Node { Object value;// 結點值 Node leftChild;// 左子樹的引用 Node rightChild;// 右子樹的引用 public Node(Object value) { super(); this.value = value; } public Node(Object value, Node leftChild, Node rightChild) { super(); this.value = value; this.leftChild = leftChild; this.rightChild = rightChild; } @Override public String toString() { return "Node [value=" + value + ", leftChild=" + leftChild + ", rightChild=" + rightChild + "]"; } }
二叉樹方法介面類
/** * 二叉樹的介面 * 可以有不同的實現類,每個類可以使用不同的儲存結構,比如順序結構、鏈式結構 * @author shangyang * */ public interface BinaryTree { /** * 是否為空樹 */ public boolean isEmpty(); /** * 樹結點數量 */ public int size(); /** * 獲取樹的高度 */ public int getHeight(); /** * 查詢指定值的結點 * @param value * @return */ public Node findKey(Object value); /** * 前序遞迴遍歷 */ public void preOrderTraverse(); /** * 中序遞迴遍歷 */ public void inOrderTraverse(); /** * 後序遞迴遍歷 */ public void postOrderTraverse(); /** * 按照層次遍歷(藉助佇列) */ public void levelOrderByStack(); /** * 中序非遞迴遍歷 */ public void inOrderByStack(); } 二叉樹介面類
實現二叉樹介面類
建立樹的根物件,並寫出建構函式。
public class LinkedBinaryTree implements BinaryTree { private Node root;// 根結點 public LinkedBinaryTree() { } public LinkedBinaryTree(Node root) { this.root = root; } }
建立二叉樹
// 建立一個二叉樹 Node nodeF = new Node("F",null,null); Node nodeE = new Node("E",null,null); Node nodeD = new Node("D",null,null); Node nodeC = new Node("C",nodeF,null); Node nodeB = new Node("B",nodeD,nodeE); Node nodeA = new Node("A",nodeB,nodeC); // 宣告nodeA為根結點 BinaryTree btree = new LinkedBinaryTree(nodeA);
判斷二叉樹是否為空
為空返回true,不為空返回false
public boolean isEmpty() { return root == null; }
輸出二叉樹結點數量
運用遞迴的思想,二叉樹結點樹 = 左子樹結點數量 + 右子樹結點數量 + 1
public int size() { System.out.println("二叉樹結點數量: "); return this.size(root); } private int size(Node root) { if(root == null) { return 0; } else { // 獲取左子樹的數量 int nl = this.size(root.leftChild); // 獲取右子樹的數量 int nr = this.size(root.rightChild); // 返回左子樹、右子樹size之和並加1 return nl + nr + 1; } }
二叉樹的深度(高度)
如果二叉樹為空,則其深度為0。
如果二叉樹只有根結點,無左右子樹,則其深度為1。
如果二叉樹結點數大於1,則用遞迴的思想計算其深度。二叉樹的深度 = 左右子樹的最大深度 + 1。
public int getHeight() { System.out.println("二叉樹的高度是 :"); return this.getHeight(root); } private int getHeight(Node root) { if(root == null) { return 0; } else { // 獲取左子樹的高度 int nl = this.getHeight(root.leftChild); // 獲取右子樹的高度 int nr = this.getHeight(root.rightChild); // 返回左子樹、右子樹較大高度並加1 return nl > nr ? nl + 1 : nr + 1; } }
在二叉樹中查詢某個值
運用遞迴的思想,將要查詢的值逐個與根結點,根結點的左子樹和右子樹的值進行比較,並進行返回。
public Node findKey(Object value) { return this.findKey(value,root); } private Node findKey(Object value,Node root) { // 結點為空,可能是整個樹的根結點,也可能是遞迴呼叫中葉子結點中左孩子和右孩子 if(root == null) { return null; } else if (root != null && root.value == value) { return root; } else {// 遞迴體 Node leftnode = this.findKey(value,root.leftChild); Node rightnode = this.findKey(value, root.rightChild); if(leftnode != null && leftnode.value == value) { return leftnode; } else if (rightnode != null && rightnode.value == value) { return rightnode; } else { return null; } } }
先序遞迴遍歷
若二叉樹非空,則依次執行如下操作:
⑴ 訪問根結點;
⑵ 遍歷左子樹;
⑶ 遍歷右子樹。
public void preOrderTraverse() { // 輸出根結點的值 if(root != null) { System.out.print(root.value + ""); // 對左子樹進行先序遍歷 // 構建一個二叉樹,根是左子樹的根 BinaryTree leftTree = new LinkedBinaryTree(root.leftChild); leftTree.preOrderTraverse(); // 對右子樹進行先序遍歷 // 構建一個二叉樹,根是左子樹的根 BinaryTree rightTree = new LinkedBinaryTree(root.rightChild); rightTree.preOrderTraverse(); } }
中序遞迴遍歷
若二叉樹非空,則依次執行如下操作:
⑴ 遍歷左子樹;
⑵ 訪問根結點;
⑶ 遍歷右子樹。
public void inOrderTraverse() { System.out.println("中序遍歷"); this.inOrderTraverse(root); System.out.println(); } private void inOrderTraverse(Node root) { if(root != null) { // 遍歷左子樹 this.inOrderTraverse(root.leftChild); // 輸出根的值 System.out.print(root.value + ""); // 遍歷右子樹 this.inOrderTraverse(root.rightChild); } }
後續遞迴遍歷
若二叉樹非空,則依次執行如下操作:
⑴ 遍歷左子樹;
⑵ 遍歷右子樹;
⑶ 訪問根結點。
public void postOrderTraverse() { System.out.println("後序遍歷"); this.postOrderTraverse(root); System.out.println(); } private void postOrderTraverse(Node root) { if(root != null) { // 遍歷左子樹 this.postOrderTraverse(root.leftChild); // 遍歷右子樹 this.postOrderTraverse(root.rightChild); // 輸出根的值 System.out.print(root.value + ""); } }
按照層次遍歷(藉助佇列)
按照從上到下、從左到右的次序進行遍歷。先遍歷完一層,再遍歷下一層,因此又叫廣度優先遍歷。
該方法可以藉助java中提供queue佇列介面來完成。 LinkedList 實現了該介面。
public void levelOrderByStack() { if(root == null) return; Queue<Node> queue = new LinkedList<Node>(); queue.add(root); while(queue.size() != 0) { int len = queue.size(); for(int i = 0; i < len; i++) { Node temp = queue.poll(); System.out.print(temp.value + ""); if(temp.leftChild != null) queue.add(temp.leftChild); if(temp.rightChild != null) queue.add(temp.rightChild); } } }
中序非遞迴遍歷(藉助棧)
(1) 若根結點不為空,則將其放如棧中,並判斷其左子樹是否為空。
(2) 若不為空,則將子樹根結點放入棧中,並繼續向下判斷,直至左子樹為空。
(3) 若棧中有結點,則將其取出,並對其右子樹根結點進行(1)(2)步驟,直至無結點或棧中元素為空。
public void inOrderByStack() { // 建立棧 Deque<Node> stack = new LinkedList<Node>(); Node current = root; while(current != null || !stack.isEmpty()) { while(current != null) { stack.push(current); current = current.leftChild; } if(!stack.isEmpty()) { current = stack.pop(); System.out.print(current.value + " "); current = current.rightChild; } } }