一:樹
我們思維中的”樹“就是一種枝繁葉茂的形象,那么數據結構中的”樹“該是怎么樣呢?對的,他是一種現實中倒立的樹。
1:術語
其實樹中有很多術語的,這個是我們學習樹形結構必須掌握的。
<1> 父節點,子節點,兄弟節點
這個就比較簡單了,B和C的父節點就是A,反過來說就是B和C是A的子節點。B和C就是兄弟節點。
<2> 結點的度
其實”度“就是”分支數“,比如A的分支數有兩個“B和C",那么A的度為2。
<3> 樹的度
看似比較莫名其妙吧,他和”結點的度“的區別就是,樹的度講究大局觀,乃樹中最大的結點度,其實也就是2。
<4> 葉結點,分支結點
葉結點就是既沒有左孩子也沒有右孩子結點,也就是結點度為0。分支節點也就是if的else的條件咯。
<5> 結點的層數
這個很簡單,也就是樹有幾層。
<6> 有序樹,無序樹
有序樹我們先前也用過,比如“堆”和“二叉排序樹”,說明這種樹是按照一定的規則進行排序的,else條件就是無序樹。
<7> 森林
現實中,很多的樹形成了森林,那在數據結構中,我們把上圖的“A”節點砍掉,那么B,C子樹合一起就是森林咯。
2: 樹的表示
樹這個結構的表示其實有很多種,常用的也就是“括號”表示法。
比如上面的樹就可以表示為:(A(B(D),(E)),(C(F),(G)))
二: 二叉樹
在我們項目開發中,很多地方都會用到樹,但是多叉樹的處理還是比較糾結的,所以俺們本着“大事化小,小事化了“的原則
把”多叉樹“轉化為”二叉樹“,那么問題就簡化了很多。
1: ”二叉樹“和”樹“有什么差異呢?
第一點: 樹的度沒有限制,而“二叉樹”最多只能有兩個,不然也就不叫二叉樹了,哈哈。
第二點:樹中的子樹沒有左右划分,很簡單啊,找不到參照點,二叉樹就有參照物咯。
2: 二叉樹的類型
二叉樹中有兩種比較完美的類型,“完全二叉樹”和“滿二叉樹”。
<1> 滿二叉樹
除葉子節點外,所有節點的度都為2,文章開頭處的樹就是這里的“滿二叉樹”。
<2> 完全二叉樹
必須要滿足兩個條件就即可: 干掉最后一層,二叉樹變為“滿二叉樹”。
最后一層的葉節點必須是“從左到右”依次排開。
我們干掉文章開頭處的節點“F和”G",此時還是“完全二叉樹”,但已經不是“滿二叉樹”了,你懂的。
3: 二叉樹的性質
二叉樹中有5點性質非常重要,也是俺們必須要記住的。
<1> 二叉樹中,第i層的節點最多有2(i-1)個。
<2> 深度為k的二叉樹最多有2k-1個節點。
<3> 二叉樹中,葉子節點樹為N1個,度為2的節點有N2個,那么N1=N2+1。
<4> 具有N個結點的二叉樹深度為(Log2 N)+1層。
<5> N個結點的完全二叉樹如何用順序存儲,對於其中的一個結點i,存在以下關系,
2*i是結點i的父結點。
i/2是結點i的左孩子。
(i/2)+1是結點i的右孩子。
4: 二叉樹的順序存儲
同樣的存儲方式也有兩種,“順序存儲”和“鏈式存儲”。
<1> 順序存儲
說實話,樹的存儲用順序結構比較少,因為從性質定理中我們都可以看出只限定為“完全二叉樹”,那么如果二叉樹不是
“完全二叉樹”,那我們就麻煩了,必須將其轉化為“完全二叉樹”,將空的節點可以用“#”代替,圖中也可看出,為了維護
性質定理5的要求,我們犧牲了兩個”資源“的空間。
<2> 鏈式存儲
上面也說了,順序存儲會造成資源的浪費,所以嘛,我們開發中用的比較多的還是“鏈式存儲”,同樣“鏈式存儲”
也非常的形象,非常的合理。
一個結點存放着一個“左指針”和一個“右指針”,這就是二叉鏈表。
如何方便的查找到該結點的父結點,可以采用三叉鏈表。
5: 常用操作
一般也就是“添加結點“,“查找節點”,“計算深度”,“遍歷結點”,“清空結點
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace BinaryTree { ///<summary> ///二叉鏈表存儲結構 ///</summry> ///<typeparam name="T"></typeparam> public class ChinaTree<T> { //字段 public T data; public ChinaTree<T> left; public ChinaTree<T> right; public int Length { get; set; } //構造函數 public ChinaTree(T nodevalue) { this.data = nodevalue; this.left = null; this.right = null; } /// <summary> /// 將指定節點插入到二叉樹中 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="tree"></param> /// <param name="node"></param> /// <param name="direction">插入做左是右</param> /// <returns></returns> public ChinaTree<T> BinTreeAddNode<T>(ChinaTree<T> tree, ChinaTree<T> node, T data, Direction direction) { //樹為空 if (tree == null) { return null; } if(tree.data.Equals(data)) { switch(direction) { case Direction.Left: if(tree.left!=null) throw new Exception("樹的左節點不為空,不能插入"); else tree.left=node; break; case Direction.Right: if (tree.right != null) throw new Exception("樹的右節點不為空,不能插入"); else tree.right = node; break; } } BinTreeAddNode(tree.left, node, data, direction); BinTreeAddNode(tree.right, node, data, direction); return tree; } //查找節點 在二叉樹中查找指定的key public ChinaTree<T> BinTreeFind<T>(ChinaTree<T> tree, T data) { if (tree == null) return null; if (tree.data.Equals(data)) return tree; return BinTreeFind(tree, data); } //計算深度 獲取二叉樹的深度 public int BinTreeLen<T>(ChinaTree<T> tree) { int leftLength; int rightLength; if (tree == null) return 0; //遞歸左子樹的深度 leftLength = BinTreeLen(tree.left); //遞歸右子樹的深度 rightLength = BinTreeLen(tree.right); if (leftLength > rightLength) return leftLength + 1; else return rightLength + 1; } //遍歷節點分為: //先序:先訪問根,然后遞歸訪問左子樹,最后遞歸右子樹。(DLR模式) //中序:先遞歸訪問左子樹,在訪問根,最后遞歸右子樹。(LDR模式) //后序:先遞歸訪問左子樹,然后遞歸訪問右子樹,最后訪問根。(LRD模式) //按層:這個比較簡單,從上到下,從左到右的遍歷節點。 //先序遍歷 public void BinTree_DLR(ChinaTree<T> tree) { if(tree==null) { return; } //先輸出根元素 Console.WriteLine(tree.data+"\t"); //然后遍歷左子樹 BinTree_DLR(tree.left); //然后遍歷右子樹 BinTree_DLR(tree.right); } //中序遍歷 public void BinTree_LDR(ChinaTree<T> tree) { if (tree == null) return; //先遍歷左子樹 BinTree_LDR(tree.left); //輸出節點值 Console.WriteLine(tree.data + "\t"); //遍歷右子樹 BinTree_LDR(tree.right); } //后序遍歷 public void BinTree_LRD<T>(ChinaTree<T> tree) { if (tree == null) return; //先遍歷左子樹 BinTree_LRD(tree.left); //再遍歷右子樹 BinTree_LRD(tree.right); //輸出節點值 Console.WriteLine(tree.data + "\t"); } //按層遍歷 public void BinTree_Level<T>(ChinaTree<T> tree) { if (tree == null) return; //申請保存空間 ChinaTree<T>[] treelist = new ChinaTree<T>[Length]; int head = 0; int tail = 0; //存放數組 treelist[tail] = tree; //循環鏈中計算tail的位置 tail = (tail + 1) % Length; while (head != tail) { var tempNode = treelist[head]; head = (head + 1) % Length; //輸出節點 Console.WriteLine(tempNode.data + "\t"); //如果左子樹不為空 則將左子樹存於數組的tail位置 if (tempNode.left != null) { treelist[tail] = tempNode.left; tail = (tail + 1) % Length; } //如果右子樹不為空,則將右子樹存於數組的tail位置 if (tempNode.right != null) { treelist[tail] = tempNode.right; tail = (tail + 1) % Length; } } } //清空二叉樹 public void BinTreeClear<T>(ChinaTree<T> tree) { //遞的結束點,歸的起始點 if (tree == null) return; BinTreeClear(tree.left); BinTreeClear(tree.right); //在歸的過程中,釋放當前節點的數據空間 tree = null; } } //枚舉左節點和右節點 public enum Direction {Left=1,Right=2} }
匯總代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { ChainTreeManager manager = new ChainTreeManager(); //插入節點操作 ChinaTree<string> tree = CreateRoot(); //插入節點數據 AddNode(tree); //先序遍歷 Console.WriteLine("\n先序結果為: \n"); manager.BinTree_DLR(tree); //中序遍歷 Console.WriteLine("\n中序結果為: \n"); manager.BinTree_LDR(tree); //后序遍歷 Console.WriteLine("\n后序結果為: \n"); manager.BinTree_LRD(tree); //層次遍歷 Console.WriteLine("\n層次結果為: \n"); manager.Length = 100; manager.BinTree_Level(tree); Console.WriteLine("\n樹的深度為:" + manager.BinTreeLen(tree) + "\n"); Console.ReadLine(); } //生成根節點 static ChinaTree<string> CreateRoot() { ChinaTree<string> tree = new ChinaTree<string>(); Console.WriteLine("請輸入根節點,方便我們生成樹\n"); tree.data = Console.ReadLine(); Console.WriteLine("根節點已經生成\n"); return tree; } //插入節點操作 static ChinaTree<string> AddNode(ChinaTree<string> tree) { ChainTreeManager mananger = new ChainTreeManager(); while (true) { ChinaTree<string> node = new ChinaTree<string>(); Console.WriteLine("請輸入要插入節點的數據\n"); node.data = Console.ReadLine(); Console.WriteLine("請輸入要查找的父節點的數據\n"); var parentData = Console.ReadLine(); bool flag = mananger.BinTreeFind(tree, parentData); if (!flag) { Console.WriteLine("未找到你輸入的父節點,請重新輸入"); continue; } Console.WriteLine("你確定要插到父節點的:1 左側,2右側"); Direction direction = (Direction)Enum.Parse(typeof(Direction), Console.ReadLine()); tree = mananger.BinTreeAddNode(tree, node, parentData, direction); Console.WriteLine("是否繼續? 1 繼續, 2 退出"); if (int.Parse(Console.ReadLine()) == 1) continue; else break; } return tree; } } public enum Direction { Left = 1, Right = 2 } public class ChinaTree<T> { //字段 public T data; public ChinaTree<T> left; public ChinaTree<T> right; } public class ChainTreeManager { //按層遍歷的Length的空間存儲 public int Length { get; set; } /// <summary> /// 將指定節點插入到二叉樹中 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="tree"></param> /// <param name="node"></param> /// <param name="direction">插入做左是右</param> /// <returns></returns> public ChinaTree<T> BinTreeAddNode<T>(ChinaTree<T> tree, ChinaTree<T> node, T data, Direction direction) { //樹為空 if (tree == null) { return null; } try { if (tree.data.Equals(data)) { switch (direction) { case Direction.Left: if (tree.left != null) throw new Exception("樹的左節點不為空,不能插入"); else tree.left = node; break; case Direction.Right: if (tree.right != null) throw new Exception("樹的右節點不為空,不能插入"); else tree.right = node; break; } } BinTreeAddNode(tree.left, node, data, direction); BinTreeAddNode(tree.right, node, data, direction); } catch (Exception ex) { Console.WriteLine(ex.Message); } return tree; } public ChinaTree<T> BinTreeChild<T>(ChinaTree<T> tree, Direction direction) { ChinaTree<T> childNode = null; if (tree == null) throw new Exception("二叉樹為空"); switch (direction) { case Direction.Left: childNode = tree.left; break; case Direction.Right: childNode = tree.right; break; } return childNode; } //查找節點 在二叉樹中查找指定的key public bool BinTreeFind<T>(ChinaTree<T> tree, T data) { bool flag = false; if (tree == null) return false; if (tree.data.Equals(data)) return true; if (tree.left != null) { return BinTreeFind(tree.left, data); } if (tree.right != null) { return BinTreeFind(tree.right, data); } return flag; } //計算深度 獲取二叉樹的深度 public int BinTreeLen<T>(ChinaTree<T> tree) { int leftLength; int rightLength; if (tree == null) return 0; //遞歸左子樹的深度 leftLength = BinTreeLen(tree.left); //遞歸右子樹的深度 rightLength = BinTreeLen(tree.right); if (leftLength > rightLength) return leftLength + 1; else return rightLength + 1; } //判斷二叉樹是否為空 public bool BinTreeisEmpty<T>(ChinaTree<T> tree) { return tree == null ? true : false; } //遍歷節點分為: //先序:先訪問根,然后遞歸訪問左子樹,最后遞歸右子樹。(DLR模式) //中序:先遞歸訪問左子樹,在訪問根,最后遞歸右子樹。(LDR模式) //后序:先遞歸訪問左子樹,然后遞歸訪問右子樹,最后訪問根。(LRD模式) //按層:這個比較簡單,從上到下,從左到右的遍歷節點。 //先序遍歷 public void BinTree_DLR<T>(ChinaTree<T> tree) { if (tree == null) { return; } //先輸出根元素 Console.Write(tree.data + "\t"); //然后遍歷左子樹 BinTree_DLR(tree.left); //然后遍歷右子樹 BinTree_DLR(tree.right); } //中序遍歷 public void BinTree_LDR<T>(ChinaTree<T> tree) { if (tree == null) return; //先遍歷左子樹 BinTree_LDR(tree.left); //輸出節點值 Console.Write(tree.data + "\t"); //遍歷右子樹 BinTree_LDR(tree.right); } //后序遍歷 public void BinTree_LRD<T>(ChinaTree<T> tree) { if (tree == null) return; //先遍歷左子樹 BinTree_LRD(tree.left); //再遍歷右子樹 BinTree_LRD(tree.right); //輸出節點值 Console.Write(tree.data + "\t"); } //按層遍歷 public void BinTree_Level<T>(ChinaTree<T> tree) { if (tree == null) return; //申請保存空間 ChinaTree<T>[] treelist = new ChinaTree<T>[100]; int head = 0; int tail = 0; //存放數組 treelist[tail] = tree; //循環鏈中計算tail的位置 tail = (tail + 1) % 100; while (head != tail) { var tempNode = treelist[head]; head = (head + 1) % 100; //輸出節點 Console.Write(tempNode.data + "\t"); //如果左子樹不為空 則將左子樹存於數組的tail位置 if (tempNode.left != null) { treelist[tail] = tempNode.left; tail = (tail + 1) % 100; } //如果右子樹不為空,則將右子樹存於數組的tail位置 if (tempNode.right != null) { treelist[tail] = tempNode.right; tail = (tail + 1) % 100; } } } //清空二叉樹 public void BinTreeClear<T>(ChinaTree<T> tree) { //遞的結束點,歸的起始點 if (tree == null) return; BinTreeClear(tree.left); BinTreeClear(tree.right); //在歸的過程中,釋放當前節點的數據空間 tree = null; } } }
運行圖: