圖1
描述的資料結構就是“樹”,其中最上面那個圈圈A稱之為根節點(root),其它圈圈稱為節點(node),當然root可以認為是node的特例。
樹跟之前學習過的線性結構不同,它是一對多的非線性結構,具有二個基本特點:
1、根節點(root)沒有前驅節點,除root之外的所有節點有且只有一個前驅節點
2、樹中的所有節點都可以有0個或多個後繼節點。
所以下面這些歪瓜咧棗,不能算是樹:
圖2
下是是一些煩人但是很重要的術語:
1、結點(Node):表示樹中的資料元素,由資料項目和資料元素之間的關係組成。在圖1中,共有10個結點。
2、結點的度(Degree of Node):結點所擁有的子樹的個數,在圖1中,結點A的度為3。
3、樹的度(Degree of Tree):樹中各結點度的最大值。在圖1中,樹的度為3。
4、葉子結點(Leaf Node):度為0的結點,也叫終端結點。在圖1中,結點E、F、G、H、I、J都是葉子結點。
5、分支結點(Branch Node):度不為0的結點,也叫非終端結點或內部結點。在圖1中,結點A、B、C、D是分支結點。
6、孩子(Child):結點子樹的根。在圖1中,結點B、C、D是結點A的孩子。
7、雙親(Parent):結點的上層結點叫該結點的雙親。在圖1中,結點B、C、D的雙親是結點A。
8、祖先(Ancestor):從根到該結點所經分支上的所有結點。在圖1中,結點E的祖先是A和B。
9、子孫(Descendant):以某結點為根的子樹中的任一結點。在圖1中,除A之外的所有結點都是A的子孫。
10、兄弟(Brother):同一雙親的孩子。在圖1中,結點B、C、D互為兄弟。
11、結點的層次(Level of Node):從根結點到樹中某結點所經路徑上的分支數稱為該結點的層次。根結點的層次規定為1,其餘結點的層次等於其雙親結點的層次加1。
12、堂兄弟(Sibling):同一層的雙親不同的結點。在圖1中,G和H互為堂兄弟。
13、樹的深度(Depth of Tree):樹中結點的最大層次數。在圖1中,樹的深度為3。
14、無序樹(Unordered Tree):樹中任意一個結點的各孩子結點之間的次序構成無關緊要的樹。通常樹指無序樹。
15、有序樹(Ordered Tree):樹中任意一個結點的各孩子結點有嚴格排列次序的樹。二叉樹是有序樹,因為二叉樹中每個孩子結點都確切定義為是該結點的左孩子結點還是右孩子結點。
16、森林(Forest):m(m≥0)棵樹的集合。自然界中的樹和森林的概念差別很大,但在資料結構中樹和森林的概念差別很小。從定義可知,一棵樹有根結點和m個子樹構成,若把樹的根結點刪除,則樹變成了包含m棵樹的森林。當然,根據定義,一棵樹也可以稱為森林。
二叉樹
通俗點講,就是每個節點最多隻能分二叉的樹(標準的廢話,呵),圖示如下:
圖3
註:二叉樹是有序樹,即每個節點下的左右子節點擺放是有順序的,所以中的(a)跟(b)被認為是二棵不同的二叉樹!
二叉樹有二種經典的特例:完全二叉樹(Complete Binary Tree) 與 滿二叉樹(Full Binary Tree)
圖4
如,所謂滿二叉樹就是:每個節點都有二個分支,且所有分葉節點都處於同一個層次。(很明顯,對於一個深度Degree為K的滿二叉樹,其節點總數為)
所謂完全二叉樹就是:葉結點只可能出現在層次最大的兩層上,並且某個結點的"左分支"下"子節點"的最大層次與"右分支"下"子節點"的最大層次相等或大1。(再講得更通俗點:除最後一層外,其它各層都是滿的,且最後一層如果出現未滿的情況,分葉節點只能在左邊,即只能空出右節點的位置)
二叉樹的數學特性:
1、 一棵非空二叉樹的第 i 層上最多有個結點(i≥1)。
比如:以滿二叉樹(a)為例,第一層只有節點a,即2的0次方=1,第二層有B,C二個節點,即2的(2-1)次方為2...
2、若規定空樹的深度為0,則深度為k的二叉樹最多有個結點(k≥0)。
這個其實上面在介紹滿二叉樹的時候,已經說過深度為k的滿二叉樹,有個節點,這個就是二叉樹的節點數極限了.
3、具有n個結點的完全二叉樹的深度k為
比如中(a)樹的總節點數為15,則深度K = lg(15) + 1 = 3 + 1 = 4
4、對於一棵非空二叉樹,如果度為0的結點數目為,度為2的結點數目為,則有
比如中(a)樹中度為0的節點為H,I,J,K,L,M共6個(即分葉節點),其它節點A,B,C,D,E,F,G都是度為2的結節有7個,有 7 = 6 + 1
5、對於具有n個結點的完全二叉樹,如果按照從上到下和從左至右的順序對所有結點從1開始編號,則對於序號為 i 的結點,有:
圖5
(1)如果 i>1,則序號為i的結點的雙親結點的序號為i/2(“/”表示整除);如果i=1,則該結點是根結點,無雙親結點。
如,隨便選一個節點,比如D(序號為4),則D的父節點序號為4/2 = 2,即節點B
(2)如果2i≤n,則該結點的左孩子結點的序號為2i;若2i>n,則該結點無左孩子。
如,隨便找個節點5(即E點),則2*5<=10,則E節點的左子節點序號為2*5 = 10即J點
(3)如果2i+1≤n,則該結點的右孩子結點的序號為2i+1;若2i+1>n,則該結點無右孩子。
如,隨便找個節點4(即D點),則2*4 + 1<=10,則D點的右子節點序列為2*4 +1 = 9,即I點
二叉樹的儲存結構
1、順序儲存
對於完全二叉樹,比5的這種,可以表示為
由剛才的數學特性5得知,只要知道了一個節點的序號i,就能確定該節點的父節點,以及左右子節點的序號(如果有左右子節點的話),所以這樣就可以將非線性樹型結構,變成一一對應的線性結構.
但是非完全二叉樹,特性5就不成立了,但我們可以給非完全二叉樹添加一些空節點,補成一棵完全二叉樹
中的^代表空節點(NULL)
這樣就能使用順序儲存了,但是缺點很明顯:浪費了儲存空間。
2、二叉鏈表格儲存體
把每個節點Node添加三個基本屬性 lChild(左子節點),rChild(右子節點),以及data(節點值),如果左子節點或右子節點為空白,則用Null表示(圖形中用^表示)
如,根據是否有前端節點引用,可分為"不帶頭結點的二叉鏈表"和"帶頭結點的二叉鏈表"
3、三叉鏈表格儲存體
二叉鏈表格儲存體方式,如果要從某節點向上找父節點或根結點,會很麻煩,所以為了改進,可以給每個Node再增加一個parent父節點屬性,就變成下面這種結構
下面來看看代碼的實現:(下面只示範了不帶頭結點的二叉鏈表實現)
節點類Node.cs(二叉鏈表的結點)
namespace 樹與二叉樹{ public class Node<T> { private T data; private Node<T> lChild;//左子節點 private Node<T> rChild;//右子節點 public Node(T data, Node<T> ln, Node<T> rn) { this.data = data; this.lChild = ln; this.rChild = rn; } public Node(Node<T> ln, Node<T> rn) { this.data = default(T); this.lChild = ln; this.rChild = rn; } public Node(T data) { this.data = data; lChild = null; rChild = null; } public Node() { data = default(T); lChild = null; rChild = null; } public T Data { get { return this.data; } set { this.data = value; } } public Node<T> LChild { get { return this.lChild; } set { this.lChild = value; } } public Node<T> RChild { get { return this.rChild; } set { this.rChild = value; } } }}
樹BiTree.cs
namespace 樹與二叉樹{ public class BiTree<T> { private Node<T> root; /// <summary> /// 根節點(屬性) /// </summary> public Node<T> Root { get { return root; } set { root = value; } } /// <summary> /// 建構函式 /// </summary> public BiTree() { root = null; } /// <summary> /// 建構函式 /// </summary> /// <param name="data"></param> public BiTree(T data) { Node<T> p = new Node<T>(data); root = p; } /// <summary> /// 建構函式 /// </summary> /// <param name="data"></param> /// <param name="ln"></param> /// <param name="rn"></param> public BiTree(T data, Node<T> ln, Node<T> rn) { Node<T> p = new Node<T>(data, ln, rn); root = p; } /// <summary> /// 判斷樹是否為空白 /// </summary> /// <returns></returns> public bool IsEmpty() { if (root == null) { return true; } else { return false; } } /// <summary> /// 擷取節點p的左子節點 /// </summary> /// <param name="p"></param> /// <returns></returns> public Node<T> GetLChild(Node<T> p) { return p.LChild; } /// <summary> /// 擷取節點p的右子節點 /// </summary> /// <param name="p"></param> /// <returns></returns> public Node<T> GetRChild(Node<T> p) { return p.RChild; } /// <summary> /// 節點p下插入左子節點data /// </summary> /// <param name="data"></param> /// <param name="p"></param> public void InsertL(T data, Node<T> p) { Node<T> tmp = new Node<T>(data); tmp.LChild = p.LChild; p.LChild = tmp; } /// <summary> /// 節點p下插入右子節點data /// </summary> /// <param name="data"></param> /// <param name="p"></param> public void InsertR(T data, Node<T> p) { Node<T> tmp = new Node<T>(data); tmp.RChild = p.RChild; p.RChild = tmp; } /// <summary> /// 刪除節點p的左子節點 /// </summary> /// <param name="p"></param> /// <returns></returns> public Node<T> DeleteL(Node<T> p) { if ((p == null) || (p.LChild == null)) { return null; } Node<T> tmp = p.LChild; p.LChild = null; return tmp; } /// <summary> /// 刪除節點p的右子節點 /// </summary> /// <param name="p"></param> /// <returns></returns> public Node<T> DeleteR(Node<T> p) { if ((p == null) || (p.RChild == null)) { return null; } Node<T> tmp = p.RChild; p.RChild = null; return tmp; } /// <summary> /// 判斷節點p是否為分葉節點 /// </summary> /// <param name="p"></param> /// <returns></returns> public bool IsLeaf(Node<T> p) { if ((p != null) && (p.LChild == null) && (p.RChild == null)) { return true; } else { return false; } } }}
其中向節點B下插入左子節點的演算法如下:
二叉樹的遍曆:
對於二叉樹而言,有四種經典的遍曆方式
1、前序走訪(也稱先序遍曆)-->即按 node-->lChild-->rChild的順序來遞迴遍曆(也就是“先自身”-->然後“左子節點”-->最後“右子節點”)
2、中序遍曆-->即按 lChild-->node-->rChild(先“左子節點”,然後“自身節點”,最後“右子節點”)的順序來遞迴遍曆
3、後序遍曆-->即按 lChild-->rChild-->node(先“左子節點”,然後“右子節點”,最後“自身節點”)的順序來遞迴遍曆
4、層順遍曆--這個不解釋,字面意思理解就行
ok,最後來一段代碼驗證+測試一把:
using System;using System.Collections;using System.Collections.Generic;namespace 樹與二叉樹{ class Program { static void Main(string[] args) { #region //構造樹 BiTree<string> tree = new BiTree<string>("A"); Node<string> root = tree.Root; tree.InsertL("B", root); tree.InsertR("C", root); Node<string> b = root.LChild; Node<string> c = root.RChild; tree.InsertL("D", b); tree.InsertR("E", b); tree.InsertL("F", c); tree.InsertR("G", c); Node<string> d = b.LChild; Node<string> e = b.RChild; tree.InsertL("H", d); tree.InsertR("I", d); tree.InsertL("J", e); #endregion Console.WriteLine("前序走訪開始>>>\n"); //前序走訪 PreOrder(root); Console.WriteLine("\n------------------------\n"); Console.WriteLine("中序遍曆開始>>>\n"); //中序遍曆 InOrder(root); Console.WriteLine("\n------------------------\n"); Console.WriteLine("後序遍曆開始>>>\n"); //後序遍曆 PostOrder(root); Console.WriteLine("\n------------------------\n"); Console.WriteLine("層序遍曆開始>>>\n"); //後序遍曆 LevelOrder(root); Console.Read(); } /// <summary> /// 前序走訪(即 root-->left-->right ) /// </summary> /// <param name="root"></param> static void PreOrder(Node<string> root) { if (root != null) { //先處理root Console.Write("{0} ", root.Data); //再處理root的左子節點 PreOrder(root.LChild); //再處理root的右子節點 PreOrder(root.RChild); } } /// <summary> /// 中序遍曆(left-->root-->right) /// </summary> /// <param name="root"></param> static void InOrder(Node<string> root) { if (root == null) { return; } //先左子節點 InOrder(root.LChild); //再根節點 Console.Write("{0} ", root.Data); //再右子節點 InOrder(root.RChild); } /// <summary> /// 後序遍曆 /// </summary> /// <param name="root"></param> static void PostOrder(Node<string> root) { if (root == null) { return; } PostOrder(root.LChild); PostOrder(root.RChild); Console.Write("{0} ", root.Data); } /// <summary> /// 層順遍曆 /// </summary> /// <param name="root"></param> static void LevelOrder(Node<string> root) { if (root != null) { Queue<Node<string>> q = new Queue<Node<string>>(); q.Enqueue(root); while (q.Count>0) { Node<string> tmp = q.Dequeue(); //先處理根節點 Console.Write("{0} ", tmp.Data); if (tmp.LChild != null) { //左子節點入隊 q.Enqueue(tmp.LChild); } if (tmp.RChild != null) { //右子節點入隊 q.Enqueue(tmp.RChild); } } } } }}
運行結果:
前序走訪開始>>>
A B D H I E J C F G
------------------------
中序遍曆開始>>>
H D I B J E A F C G
------------------------
後序遍曆開始>>>
H I D J E B F G C A
------------------------
層序遍曆開始>>>
A B C D E F G H I J