數據結構7 二叉樹
這篇文章開始總結 樹和二叉樹。
什麽是樹呢?
1、樹的定義
(1)有且僅有一個特定的稱為根(root) 的節點。
(2)當 n>1 時,其余節點可分為 m(m>0) 個互不相交的集合。其中每個集合本身又是一個棵樹,並稱為根的子樹。
2、樹的表示方法
最常見的是 樹形表示法 和 廣義表表示法,下面是樹形表示法,如圖所示。
上圖的 廣義表表示法為:(A(B(D,E),C(F,G)))
3、常見的術語
(1)父節點,孩子節點,兄弟節點。以上圖為例,A是B和C的父節點,B和C是A的孩子節點,B和C之間互為兄弟節點。
(2)節點的度和樹的度。節點的度即節點有幾個分支,比如節點A有兩個分支B和C,那麽節點A的度就是2,樹的度即為一棵樹中節點的最大度數,所以上圖的樹的度也是2。
(3)有序樹和無序樹。如果將樹中節點的子樹看成是從左至右依次有序且不能交換,則稱該樹為有序樹,否則稱為無序樹。
(4)森林。在上圖中,如果將根節點A拿掉,那麽B子樹和C子樹合並就是森林了。
(5)二叉樹。二叉樹是一種特殊的樹。它的每個節點最多只有兩棵子樹。
4、二叉樹的常見性質
性質1:在二叉樹的第 i 層上最多有 2i-1 個節點 (i>=1)
性質2:深度為 k 的二叉樹最多有 2k-1 個節點 (k>=1)
性質3:滿二叉樹:一棵深度為 k 且有 2k-1 個節點的二叉樹稱為 滿二叉樹。
完全二叉樹:一棵深度為 k 的二叉樹,其前 k-1 層是一棵滿二叉樹,而最下面一層(即第k層) 上的節點都集中在該層最左邊
滿二叉樹一定是完全二叉樹,但完全二叉樹不一定是滿二叉樹。
下面是 滿二叉樹 和 完全二叉樹 的圖示:
5、二叉樹的兩種存儲結構
5-1:順序存儲
對於完全二叉樹而言,可以使用順序存儲結構。但對於一般的二叉樹而言,使用順序存儲結構會有兩個缺點:一、如果不是完全二叉樹,則必須將其轉化為完全二叉樹;二、增加了很多虛節點,浪費資源空間。
5-2:鏈式存儲
鏈式存儲是最常用的一種二叉樹存儲結構。每個節點設置三個域,分別是值域、左指針域和右指針域,用 data 表示值域,lchild 和 rchild 分別表示指向左子樹、右子樹的指針域。如下圖:
6、二叉樹的常見操作
6-1:插入節點
思路:首先找到要插入節點的父節點,然後確定插入到父節點的左邊還是右邊,最後將節點插入。
6-2:查找節點
思路:運用遞歸查找。
6-3:計算樹的深度
思路:分別遞歸左子樹和右子樹,取長度較大的那一個作為整個樹的深度。
6-4:遍歷之 先序遍歷
思路:先訪問根節點,然後遍歷左子樹,再遍歷右子樹
6-5:遍歷之 中序遍歷
思路:先遍歷左子樹,再訪問根節點,最後遍歷右子樹
6-6:遍歷之 後序遍歷
思路:先遍歷左子樹,再遍歷右子樹,最後訪問根節點
6-7:遍歷之 按層遍歷
思路:從上到下,從左到右遍歷節點
7、二叉樹的常見操作的代碼實現
代碼如下:
節點類Node.java
public class Node { public int data; // 數據域 public Node left; // 左孩子 public Node right; // 右孩子 }
BinTree.java
import java.util.Scanner; public class BinTree { // 按層遍歷的存儲空間長度 public static int length; /** * 生成根節點 */ public static Node createRoot() { Node root = new Node(); System.out.println("請輸入根節點,以便生成樹:"); root.data = new Scanner(System.in).nextInt(); System.out.println("根節點生成成功"); return root; } /** * 插入節點 * * @param root 二叉樹的根節點 * @return 返回二叉樹的根節點 * @throws Exception */ public static Node insert(Node root) throws Exception { while (true) { System.out.println("請輸入待插入節點的數據:"); Node node = new Node(); node.data = new Scanner(System.in).nextInt(); // 獲取父節點數據 System.out.println("請輸入它(待插入節點) 的父節點:"); int parentNodeData = new Scanner(System.in).nextInt(); // 確定插入方向 System.out.println("請確定要插入到父節點的:1 左側, 2 右側"); int direction = new Scanner(System.in).nextInt(); // 插入節點 root = insertNode(root, node, parentNodeData, direction); if (root == null) { System.out.println("未找到父節點,請重新輸入!"); continue; } System.out.println("插入成功,是否繼續? 1繼續,2退出"); if (new Scanner(System.in).nextInt() == 1) continue; else break; // 退出循環 } return root; } /** * 從insert()方法抽取出來的需要遞歸的部分 * * @param root 二叉樹的根節點 * @param node 待插入節點 * @param parentNodeData 待插入節點的父節點 * @param direction 插入到左邊還是右邊 * @return 返回二叉樹的根節點 * @throws Exception */ public static Node insertNode(Node root, Node node, int parentNodeData, int direction) throws Exception { if (root == null) return null; // 找到父節點 if (root.data == parentNodeData) { switch (direction) { case 1: if (root.left != null) throw new Exception("左節點已存在,不能插入!"); else root.left = node; break; case 2: if (root.right != null) throw new Exception("右節點已存在,不能插入!"); else root.right = node; break; } } // 向左子樹查找父節點(遞歸) insertNode(root.left, node, parentNodeData, direction); // 向右子樹查找父節點(遞歸) insertNode(root.right, node, parentNodeData, direction); return root; } /** * 查找節點 */ public static boolean getNode(Node root, int data) { if (root == null) return false; // 查找成功 if (root.data == data) return true; // 遞歸查找 boolean result1 = getNode(root.left, data); boolean result2 = getNode(root.right, data); return result1 || result2; } /** * 獲取二叉樹的深度 * 思路:分別遞歸左子樹和右子樹,取長度較大的那一個作為整個樹的深度 * * @param root 二叉樹的根節點 * @return */ public static int getLength(Node root) { if (root == null) return 0; int leftLength, rightLength; // 遞歸左子樹的深度 leftLength = getLength(root.left); // 遞歸右子樹的深度 rightLength = getLength(root.right); if (leftLength > rightLength) return leftLength + 1; else return rightLength + 1; } /** * 先序遍歷 * 思路:先訪問根節點,然後遍歷左子樹,再遍歷右子樹 * * @param root 根節點 */ public static void DLR(Node root) { if (root == null) return; // 輸出節點的值 System.out.print(root.data + " "); // 遞歸遍歷左子樹 DLR(root.left); // 遞歸遍歷右子樹 DLR(root.right); } /** * 中序遍歷 * 思路:先遍歷左子樹,再訪問根節點,最後遍歷右子樹 * * @param root 根節點 */ public static void LDR(Node root) { if (root == null) return; // 遍歷左子樹 LDR(root.left); // 輸出節點的值 System.out.print(root.data + " "); // 遍歷右子樹 LDR(root.right); } /** * 後序遍歷 * 思路:先遍歷左子樹,再遍歷右子樹,最後訪問根節點 * * @param root 根節點 */ public static void LRD(Node root) { if (root == null) return; // 遍歷左子樹 LRD(root.left); // 遍歷右子樹 LRD(root.right); // 輸出節點的值 System.out.print(root.data + " "); } /** * 按層遍歷 * 思路:從上到下,從左到右遍歷節點 * * @param root 根節點 */ public static void traversalLevel(Node root) { if (root == null) return; int head = 0; int tail = 0; // 申請保存空間 Node[] nodeList = new Node[length]; // 將當前二叉樹保存到數組中 nodeList[tail] = root; // 計算tail的位置 tail = (tail + 1) % length; // 除留余數法 while (head != tail) { Node tempNode = nodeList[head]; // 計算head的位置 head = (head + 1) % length; // 輸出節點的值 System.out.print(tempNode.data + " "); // 如果左子樹不為空,則將左子樹保存到數組的tail位置 if (tempNode.left != null) { nodeList[tail] = tempNode.left; // 重新計算tail的位置 tail = (tail + 1) % length; } // 如果右子樹不為空,則將右子樹保存到數組的tail位置 if (tempNode.right != null) { nodeList[tail] = tempNode.right; // 重新計算tail的位置 tail = (tail + 1) % length; } } } }
BinTreeTest.java
public class BinTreeTest { public static void main(String[] args) throws Exception { System.out.println("*******鏈式存儲的二叉樹*******"); // 創建根節點 Node root = BinTree.createRoot(); // 1、插入節點 System.out.println("********插入節點*******"); BinTree.insert(root); // 2、查找節點 System.out.print("查找data為2的節點是否存在:"); boolean result = BinTree.getNode(root, 2); System.out.println(result); // 3、獲取二叉樹的深度 System.out.println("當前二叉樹的深度為:" + BinTree.getLength(root)); // 4、先序遍歷 System.out.print("先序遍歷:"); BinTree.DLR(root); System.out.println(""); // 5、中序遍歷 System.out.print("中序遍歷:"); BinTree.LDR(root); System.out.println(""); // 6、後序遍歷 System.out.print("後序遍歷:"); BinTree.LRD(root); System.out.println(""); // 7、按層遍歷 System.out.print("按層遍歷:"); BinTree.length = 100; BinTree.traversalLevel(root); System.out.println(""); } }
運行結果:
歡迎轉載,但請保留文章原始出處
本文地址:http://www.cnblogs.com/nnngu/p/8308220.html
數據結構7 二叉樹