一.樹中的節點關係和一些概念node
1.基本概念數組
樹中節點數可使用n來表示this
空樹:n爲0的樹spa
節點的度:指節點的子節點數目,如上圖中B節點爲一度,D節點爲三度3d
父子兄弟關係:如上圖中D是G的父節點,H是D的子節點,G是H的兄弟節點,I是J的堂兄弟節點(這個概念不重要)code
樹的層次:仍是如上圖,A節點爲第一層,B、C節點爲第二層,D、E、F節點爲第三層,G、H、I、J節點爲第四層blog
樹的深度:仍是上圖中,總共有四個層次的節點,稱樹的深度爲4排序
樹中的一些注意事項:1)一個子節點只能有一個父節點,可是一個父節點的子節點數目沒有限制遞歸
2)子樹之間不能相交ci
3)樹只能有一個根節點
2.樹的存儲
樹能夠採用鏈式存儲,能夠採用如下的一些方式存儲:
1)在節點中記錄父節點的位置,即父節點存儲法。這個方式能夠很方便找到父節點甚至父節點的父節點,可是尋找子節點時卻須要遍歷查找;
2)在節點中記錄全部子節點的位置,即子節點存儲法。這個方式一樣方便找子節點,可是尋找父節點一樣須要遍歷;
3)在節點中記錄第一個子節點和其餘兄弟節點的位置,即孩子兄弟存儲法。這個方法找子節點和兄弟節點很方便,可是找父節點一樣要遍歷。
二.二叉樹
二叉樹中每一個節點的子節點最多隻能有兩個,稱爲左右子節點,左右子節點下方的樹稱爲左右子樹。
1.一些特殊的二叉樹
左斜樹:全部節點只有左子節點的樹。
右斜樹:全部節點只有右子節點的樹。
滿二叉樹:對於一個k層的二叉樹,k-1層的節點都有兩個子節點,第k層的節點都有0個子節點,這樣的二叉樹稱爲滿二叉樹。
徹底二叉樹:對一個有k層的滿二叉樹,第k層的節點有2k-1個,若是將這些節點從右向左連續刪除0-2k-1個後就獲得一個徹底二叉樹。滿二叉樹是特殊的徹底二叉樹。下圖分別是4層的滿樹從右向左連續刪除6個節點和3個節點獲得的兩個徹底二叉樹。
2.二叉樹的存儲
對一個k層的滿二叉樹,每一層的節點數分別是20、21、......、2k-1,總節點數是2k-1。以下圖,將這個滿二叉樹的全部節點一層一層從左到右編號,能夠發現一個有趣的現象:每一個節點的父節點編號都是這個節點編號除以2的商,每一個節點的子節點編號都是這個節點編號乘以2的積(左子節點)或者乘以2加1的和(右子節點)。
根據這個特性可使用順序的方式存儲二叉樹,給每一個節點編號,每一個節點的子節點和父節點均可以經過編號運算獲得。對於非徹底二叉樹,編號時將空缺的位置一同編號便可。下面是三個非徹底二叉樹的編號方式:
所以二叉樹的存儲可使用數組進行順序存儲,空缺的節點置爲空便可。同時也因爲空缺的節點都置爲空,所以像右斜樹等不徹底二叉樹會形成嚴重的空間浪費,所以鏈表存儲的方式也能夠採用。
3.二叉樹的遍歷
1)前序遍歷:從根節點開始,先輸出當前節點的數據,再依次遍歷輸出左節點和右節點。
2)中序遍歷:從根節點開始,先輸出當前左節點的數據,再輸出當前節點的數據,最後輸出當前的右節點的數據。
3)後序遍歷:從根節點開始,先輸出當前節點左節點的數據,再輸出當前節點右節點的數據,最後輸出當前節點的數據。
4)層序遍歷:從樹的第一層開始,從上到下逐層遍歷,每一層從左到右依次輸出。
4.二叉樹的代碼實現(C#),這裏使用順序存儲實現了二叉樹的數值添加和幾種遍歷,沒有實現刪除的方法。
class BiTree<T> { private T[] data; private int count = 0; //當前二叉樹存儲的數據量 public BiTree(int capacity) //當前二叉樹的數據容量 { data = new T[capacity]; } /// <summary> /// 向二叉樹中添加數據或者修改二叉樹中的數據 /// </summary> /// <param name="item"></param>須要存儲的數據 /// <param name="index"></param>須要存儲的數據的編號 /// <returns></returns> public bool Add(T item,int index) { //校驗二叉樹是否存滿 if (count >= data.Length) return false; data[index - 1] = item; count++; return true; } public void Traversal() { FirstTravalsal(1); Console.WriteLine(); MiddleTravalsal(1); Console.WriteLine(); LastTravalsal(1); Console.WriteLine(); LayerTravalsal(); } /// <summary> /// 前序遍歷 /// </summary> /// <param name="index"></param>遍歷的數據的編號 private void FirstTravalsal(int index) { //校驗編號是否存在 if (index > data.Length) return; //校驗數據是否存在,當前位置沒有數據則存儲爲-1 if (data[index - 1].Equals(-1)) return; //輸出當前數據 Console.Write(data[index - 1] + " "); //計算左右子節點的下標 int leftNumber = index * 2; int rightNumber = index * 2 + 1; //遞歸遍歷左右子節點 FirstTravalsal(leftNumber); FirstTravalsal(rightNumber); } /// <summary> /// 中序遍歷 /// </summary> /// <param name="index"></param>遍歷的數據的編號 private void MiddleTravalsal(int index) { //校驗編號是否存在 if (index > data.Length) return; //校驗數據是否存在,當前位置沒有數據則存儲爲-1 if (data[index - 1].Equals(-1)) return; //計算左右子節點的下標 int leftNumber = index * 2; int rightNumber = index * 2 + 1; //遞歸遍歷左子節點 FirstTravalsal(leftNumber); //輸出當前數據 Console.Write(data[index - 1] + " "); //遞歸遍歷右子節點 FirstTravalsal(rightNumber); } /// <summary> /// 後序遍歷 /// </summary> /// <param name="index"></param>遍歷的數據的編號 private void LastTravalsal(int index) { //校驗編號是否存在 if (index > data.Length) return; //校驗數據是否存在,當前位置沒有數據則存儲爲-1 if (data[index - 1].Equals(-1)) return; //計算左右子節點的下標 int leftNumber = index * 2; int rightNumber = index * 2 + 1; //遞歸遍歷左子節點 FirstTravalsal(leftNumber); //遞歸遍歷右子節點 FirstTravalsal(rightNumber); //輸出當前數據 Console.Write(data[index - 1] + " "); } /// <summary> /// 層序遍歷 /// </summary> private void LayerTravalsal() { for(int i = 0;i < data.Length;i++) { //校驗當前數據是否爲空 if (data[i].Equals(-1)) continue; //輸出遍歷的數據 Console.Write(data[i] + " "); } } }
三.二叉排序樹
二叉排序樹的節點位置和節點的大小有關,從根節點開始判斷,比當前節點小就往當前節點的左子樹上移動,反之往當前節點的右子樹上移動,一直判斷直到移動到的位置沒有節點,這個位置就是節點的放置位置。以下圖所示,鏈接線上的數字是節點的放置順序:
能夠看到,一樣的數據,最後存儲出來的二叉樹形狀和數據的放置順序有關。
下面是二叉排序樹的實現(C#),這裏使用鏈表實現了二叉排序樹的節點添、查找和刪除。
class BSNode { public BSNode LeftChild{get;set;} public BSNode RightChild { get; set; } public BSNode Parent { get; set; } public int Data { get; set; } public BSNode() { } public BSNode(int item) { this.Data = item; } }
class BSTree { //記錄根節點位置 public BSNode Root { get; set; } /// <summary> /// 添加數據 /// </summary> /// <param name="item"></param>要添加的數據,以int爲例,也能夠拓展爲一個泛型 public void AddNode(int item) { //新建一個node BSNode newNode = new BSNode(item); //判斷樹中有沒有節點,沒有節點當前節點做爲根節點,有節點將數據放入節點中 if (Root == null) Root = newNode; else { //定義一個臨時節點記錄當前正在訪問的節點位置 BSNode temp = Root; //死循環,須要不斷判斷節點應該往當前節點左邊仍是右邊放置,且不知道要循環判斷多少次 while (true) { //若是要放置的數據大於當前節點,說明要放置的節點應該往右邊放 if(item >= temp.Data) { //判斷當前節點的右邊子節點位置是否已經有節點,若是沒有直接放置而後跳出循環 if (temp.RightChild == null) { temp.RightChild = newNode; newNode.Parent = temp; break; } //當前節點右節點位置有數據的狀況下,將臨時節點置爲當前節點的右節點,繼續循環判斷應該往這個節點的哪一邊放置 else { temp = temp.RightChild; } } //若是要放置的數據不是大於當前節點,說明要放置的節點應該往左邊放 else { if (temp.LeftChild == null) { temp.LeftChild = newNode; newNode.Parent = temp; break; } else { temp = temp.LeftChild; } } } } } /// <summary> /// 採用中序遍歷能夠實現數據由小到大輸出 /// </summary> /// <param name="node"></param>遍歷輸出node節點及其子節點 public void MiddleTraversal(BSNode node) { if (node == null) return; MiddleTraversal(node.LeftChild); Console.Write(node.Data + " "); MiddleTraversal(node.RightChild); } /// <summary> /// 查找數據是否在樹中(遞歸方式) /// </summary> /// <param name="item"></param>要查找的數據 /// <param name="node"></param>在node節點及其子孫節點中查找 /// <returns></returns> public bool Find1(int item,BSNode node) { //校驗當前節點是否爲空 if (node == null) return false; //判斷節點的數據是否和要查找的數據相同 else if (item == node.Data) return true; //判斷要查找的數據和當前數據的大小,決定是繼續在左子樹中查找仍是在右子樹中查找 else if (item > node.Data) return Find1(item, node.RightChild); else return Find1(item, node.LeftChild); } /// <summary> /// 查找數據是否在樹中(循環方式) /// </summary> /// <param name="item"></param>要查找的數據 /// <param name="node"></param>在node節點及其子孫節點中查找 /// <returns></returns> public bool Find2(int item, BSNode node) { BSNode temp = node; while (true) { //校驗當前節點是否爲空 if (temp == null) return false; //判斷節點的數據是否和要查找的數據相同 else if (item == temp.Data) return true; //判斷要查找的數據和當前數據的大小,決定是繼續在左子樹中查找仍是在右子樹中查找 else if (item > temp.Data) temp = temp.RightChild; else temp = temp.LeftChild; } } /// <summary> /// 根據數據查找並刪除存儲數據的節點 /// </summary> /// <param name="item"></param>要刪除的數據 /// <returns></returns> public bool DeleteNode(int item) { //首先須要查找要刪除的節點是否存在,使用臨時節點temp記錄,若是存在才能刪除,不然不能刪除 BSNode temp = Root; while (true) { //校驗當前節點是否爲空 if (temp == null) return false; //判斷節點的數據是否和要查找的數據相同,相同就須要刪除這個節點 else if (item == temp.Data) { DeleteNode(temp); return true; } //判斷要查找的數據和當前數據的大小,決定是繼續在左子樹中查找仍是在右子樹中查找 else if (item > temp.Data) temp = temp.RightChild; else temp = temp.LeftChild; } } /// <summary> /// 刪除指定的節點 /// </summary> /// <param name="node"></param>要刪除的節點 private void DeleteNode(BSNode node) { //判斷當前節點是否爲根節點,不是根節點刪除時須要修改當前節點的父節點的引用指向,是根節點須要修改Root的值 if (node.Parent != null) { //分爲四種狀況,分別是這個節點沒有子節點、只用左子節點、只有右子節點和有左右兩個子節點 //沒有子節點直接修改父節點的引用,並刪除節點 if (node.LeftChild == null && node.RightChild == null) { if (node.Parent.RightChild == node) node.Parent.RightChild = null; else node.Parent.LeftChild = null; node = null; } //只有左子節點或者右子節點須要更改父節點的引用,並更改相應子節點的父節點引用 else if (node.LeftChild == null && node.RightChild != null) { if (node.Parent.RightChild == node) node.Parent.RightChild = node.RightChild; else node.Parent.LeftChild = node.RightChild; node.RightChild.Parent = node.Parent; node = null; } else if (node.LeftChild != null && node.RightChild == null) { if (node.Parent.RightChild == node) node.Parent.RightChild = node.LeftChild; else node.Parent.LeftChild = node.LeftChild; node.LeftChild.Parent = node.Parent; node = null; } //左右子節點都有的狀況下將右子樹的最小值的數據複製到當前節點,而後去刪除右子樹的最小值 else { BSNode temp = node.RightChild; while (true) { if (temp.LeftChild != null) temp = temp.LeftChild; else break; } node.Data = temp.Data; DeleteNode(temp); } } //當前節點是根節點的狀況下和不是根節點的刪除相似,也是四種狀況,不過須要修改的是Root的引用和子節點的父節點引用,不用管父節點 else { if (node.LeftChild == null && node.RightChild == null) { node = null; Root = null; } else if (node.LeftChild == null && node.RightChild != null) { node.RightChild.Parent = null; Root = node.RightChild; node = null; } else if (node.LeftChild != null && node.RightChild == null) { node.LeftChild.Parent = null; Root = node.LeftChild; node = null; } else { BSNode temp = node.RightChild; while (true) { if (temp.LeftChild != null) temp = temp.LeftChild; else break; } node.Data = temp.Data; DeleteNode(temp); } } } }