树一:邂逅入门篇
一、樹的概念
樹是一種典型的非線性結(jié)構(gòu),是表達有層次特性的圖結(jié)構(gòu)的一種方法。
1.1 基本術(shù)語
| 空樹 | 當(dāng)n=0 時稱為空樹。 |
| 根結(jié)點 | 根結(jié)點是一個沒有雙親結(jié)點的結(jié)點。一棵樹最多一個。例如:A |
| 邊 | 結(jié)點之間的連線 |
| 葉子結(jié)點 | 沒有孩子結(jié)點的結(jié)點。例如:N、O、P |
| 兄弟結(jié)點 | 同一雙親的孩子結(jié)點; 堂兄結(jié)點:同一層上結(jié)點 |
| 祖先結(jié)點 | 從根到該結(jié)點的所經(jīng)分支上的所有結(jié)點 |
| 子孫結(jié)點 | 以某結(jié)點為根的子樹中任一結(jié)點都稱為該結(jié)點的子孫 |
| 結(jié)點層 | 根結(jié)點的層定義為1;根的孩子為第二層結(jié)點,依此類推; |
| 樹的深度 | 樹中最大的結(jié)點層 |
| 樹的高度 | 跟樹的深度一個意思。樹中所有結(jié)點高度的最大值 |
| 結(jié)點的度 | 結(jié)點子樹的個數(shù) |
| 樹的度 | 樹中最大的結(jié)點度。 |
| 分枝結(jié)點 | 度不為0的結(jié)點; |
| 有序樹 | 子樹有序的樹,如:家族樹; |
| 無序樹 | 不考慮子樹的順序 |
注意:有寫書定義書高和層是從0開始的。如:根結(jié)點在0層
- 樹的度
- 如果樹除了葉子結(jié)點外,其余每一個結(jié)點只有一個孩子結(jié)點,則這種樹稱為斜樹; 所有的結(jié)點都只有左子樹的二叉樹叫左斜樹。所有結(jié)點都是只有右子樹的二叉樹叫右斜樹。
1.2 二叉樹
如果一棵樹的每個結(jié)點有0 、1 后者 2個結(jié)點(最多兩個孩子結(jié)點),那么這棵樹稱為二叉樹??諛湟彩且豢糜行У亩鏄?。二叉樹的子樹還是二叉樹。實際上可以遞歸定義二叉樹。(因為這個特性,對二叉樹的操作常常使用遞歸算法)
- 滿二叉樹/滿樹: 二叉樹中的每個結(jié)點恰好有兩個孩子結(jié)點且所有葉子結(jié)點都在同一層。
- 完全二叉樹/完全樹:對一顆具有n個結(jié)點的二叉樹按層編號,如果編號為i(1<=i<=n) 的結(jié)點與同樣深度的滿二叉樹中編號為i的結(jié)點在二叉樹中位置完全相同,則這棵二叉樹稱為完全二叉樹
倒數(shù)第二層是滿的,且最后一層的葉子結(jié)點從左至右填充
- 平衡二叉樹: 如果樹中的每個結(jié)點的子樹的高度差不大于1,則樹稱為高度平衡的或者平衡的。
- 完全平衡樹: 若二叉樹中的每個結(jié)點有兩棵高度相等的子樹,則該樹稱為完全平衡樹。唯一的完全平衡二叉樹是滿樹。
- 二叉搜索樹
二叉查找樹是一棵二叉樹、其結(jié)點含有comparable類型的對象,并遵循以下規(guī)則:
- 平衡二叉查找樹(AVL 樹)
是平衡樹和二叉搜索樹的結(jié)合
在HB(k)中,如果k=1,那么這樣的二叉搜索叫做AVL樹。即一棵AVL樹是帶有平衡條件的二叉搜索樹。 左子樹和右子樹的高度最多不能超過1
- 紅黑色
在紅黑樹中,每個結(jié)點關(guān)聯(lián)一個額外的屬性:紅色或者黑色中的一種顏色。
定義:紅黑樹是一棵滿足以下性質(zhì)的二叉搜索樹:
1.3 滿樹(滿二叉樹)或完全樹(完全二叉樹)的高度
其中h是樹的高度,如果滿樹的結(jié)點樹為n,則有
即含有n個結(jié)點的滿樹的高度是 log2(n+1)。含n個結(jié)點的完全樹的高度是對 log2(n+1)向上取整。
含有n個結(jié)點的完全二叉樹或滿二叉樹的高度是對log2(n+1)向上取整
1.4 其他、 二叉樹的應(yīng)用
- 編譯器中的表達式
- 用于數(shù)據(jù)壓縮算法中哈弗曼編碼樹
- 二叉搜索樹(查找樹)BST
- 優(yōu)先隊列(PQ)
……
1.5 二叉樹的結(jié)構(gòu)
為了簡單起見,數(shù)據(jù)設(shè)定為整數(shù)
//java語言描述 public class BinaryTreeNode {private int data;private BinaryTreeNode leftTree;private BinaryTreeNode rightTree;…… }1.6 二叉樹的操作
- 基本操作
- 插入
- 刪除
- 查找
- 遍歷
- 輔助操作
- 樹大小
- 樹高
- 樹最大層
- 給定兩個或多個節(jié)點,找最近公共祖先
……
二 、樹的遍歷
2.1 二叉樹的遍歷
為了對樹結(jié)構(gòu)進行處理,需要一種機制來遍歷樹中的結(jié)點。在遍歷過程中,每個結(jié)點只能被處理一次,但可以被訪問多次。
- 前序遍歷/先序遍歷:
在訪問根的子樹之前訪問根。然后訪問根的左子樹中所有的結(jié)點,再訪問根的右子樹中的所有結(jié)點。
遞歸方式
public static void preOrder(BinaryTreeNode root) {if(null != root) {System.out.print(root.getData() + " ");preOrder(root.getLeftTree());preOrder(root.getRightTree());}}非遞歸方式
為了模擬遞歸:首先處理當(dāng)前結(jié)點,在遍歷左子樹之前,把當(dāng)前結(jié)點保留到棧中,當(dāng)遍歷完左子樹后,將該元素出棧,然后找打其右子樹進行遍歷。直到棧為空為止。
public static void preOrderNonRecursive(BinaryTreeNode root) {if(null == root) {return;}Stack<BinaryTreeNode> s = new Stack<BinaryTreeNode>(); // FILOwhile (true) {while (null != root) {System.out.print(root.getData() + " ");s.push(root);root = root.getLeftTree();}if(s.isEmpty()) {break;}root = s.pop();root = root.getRightTree();}}- 中序遍歷:
在中序遍歷中,根結(jié)點的訪問在兩棵子樹的遍歷中間完成。
遞歸方式:
public static void inOrder(BinaryTreeNode root) {if(null != root) {inOrder(root.getLeftTree());System.out.print(root.getData() + " ");inOrder(root.getRightTree());} }非遞歸方式:
非遞歸中序遍歷類似前序遍歷。唯一區(qū)別是,首先要移動到結(jié)點的左子樹,完成左子樹的遍歷后,再將結(jié)點出棧進行處理。
public static void inOrderNonRecursive(BinaryTreeNode root) {if(null == root) return;Stack<BinaryTreeNode> stack = new Stack<BinaryTreeNode>();while (true) {while (null != root) {stack.push(root);root = root.getLeftTree();}if(stack.isEmpty()) return;BinaryTreeNode node = stack.pop();System.out.print(node.getData() + " ");root = node.getRightTree();}}- 后序遍歷:
訪問二叉樹的根的子樹中的結(jié)點之后訪問樹的根
遞歸方式:
public static void postorder(BinaryTreeNode root) {if(null != root) {postorder(root.getLeftTree());postorder(root.getRightTree());System.out.print(root.getData() + " ");}}非遞歸方式:
①:前序、中序遍歷中,當(dāng)元素出棧后就不需要再次訪問該結(jié)點了,但是后序遍歷中,每個結(jié)點需要訪問兩次。
②:當(dāng)從棧中出棧一個元素時,檢查這個元素與棧頂元素的右子樹是否相同。如果相同,則說明已經(jīng)完成了左、右子樹的遍歷。此時,只需要再將棧頂元素出棧一次病輸出該結(jié)點數(shù)據(jù)即可。
- 層序遍歷:
從根開始,每次訪問一層中的結(jié)點。在同一層中,從左至右訪問。
層序遍歷需要借助隊列來完成
public static void levelOrder(BinaryTreeNode root) {if(null == root) return;Queue<BinaryTreeNode> queue = new ArrayDeque<>();queue.add(root);BinaryTreeNode temp = null;while (!queue.isEmpty()) {temp = queue.poll();//處理當(dāng)前結(jié)點System.out.print(temp.getData() + " ");if(null != temp.getLeftTree()) {queue.add(temp.getLeftTree());}if(null != temp.getRightTree()) {queue.add(temp.getRightTree());}}}廣度優(yōu)先遍歷:層序遍歷是廣度優(yōu)先遍歷的示例
深度優(yōu)先遍歷:前序遍歷是深度優(yōu)先遍歷的示例
2.2一般樹的遍歷
一般樹的遍歷有層序、前序和后序、對一般樹而言,中序遍歷不好定義。
三、樹的示例
一些使用樹來組織數(shù)據(jù)的示例。
3.1 表達式樹
可以用二叉樹來表示其運算符為二元運算符的代數(shù)表達式。孩子的次序要與操作樹的次序想匹配。這樣的二叉樹稱為表達式樹。
事實上,不需要括號,樹也能得到表示中的運算符的次序。代數(shù)表達式有不同的寫法。正常書寫的表達式,即每個二元運算符出現(xiàn)在兩個操作數(shù)的中間,稱為中綴表達式。前綴表達式將每個運算符都放在其兩個操作的前面,后綴表達式將每個運算符都放在其兩個操作數(shù)的后面。
3.2 二叉查找樹 / 二叉搜索樹
二叉查找樹是一棵二叉樹、其結(jié)點含有comparable類型的對象,并遵循以下規(guī)則:
- 結(jié)點中的數(shù)據(jù)大于結(jié)點左子樹中的所有數(shù)據(jù)。
- 結(jié)點中的數(shù)據(jù)小于結(jié)點的右子樹中的所有數(shù)據(jù)。
- 左子樹和右子樹也都必須是二叉查找樹
例如:字符串二叉查找樹、Jared大于Jared的左子樹中的所有名字,但小于Jared的右子樹中的所有名字。
下面是也是一棵二叉查找樹
注意: 上面二叉查找樹的定義,隱含表明樹中的所有項都是不同的。但允許樹中有重復(fù)的值
二叉查找樹的形態(tài)是不唯一的。即對同樣的一組數(shù)據(jù),可以組成幾棵不同的二叉查找樹。例如:
3.3 堆
堆(heap) 是其結(jié)點含有 Comparable對象的一棵完全二叉樹,且滿足以下條件:每個結(jié)點的對象不小于(或不大于)其后代的對象。 在最大堆中,結(jié)點中的對象大于或等于其后代的對象。 在最小堆中,關(guān)系是小于或等于。
最大/小堆的任何結(jié)點的子樹仍然是最大/小堆。
可以用堆實現(xiàn)優(yōu)先隊列
小結(jié)
本篇是對樹的一個入門,講解了樹的一些基本概念、并對二叉樹的三種遍歷做了全面的介紹,給出了java描述。后面講解了一些其他平衡樹的知識,也給出了一些樹的示例。
參考
- 《數(shù)據(jù)結(jié)構(gòu)與算法經(jīng)典問題解析-Java語言描述》
- 《數(shù)據(jù)結(jié)構(gòu)與抽象Java語言描述第4版》
- 參考 【https://xiaozhuanlan.com/topic/7189032546】
總結(jié)
- 上一篇: win7安装用友U8教程详解
- 下一篇: 简单音乐播放器,上一曲下一曲,暂停