不少初級點的程序員會認爲樹結構無用論,也有初級程序員僅僅覺得只有面試纔會用到,還有自認爲實際工做用不到(我身邊工做好幾年程序員懂樹結構也沒有幾個),其實歸根到底仍是不清楚樹的實際用途,下面分享我參加培訓時候一個小尷尬。html
由於項目數據量很大(有不少表數據量都上億的),對寫sql能力要求很高,項目組會常常組織些數據庫方面的培訓,前段時間又參加公司一個SQL原理分析的一個培訓,在培訓中講師問「爲何SQL走索引查詢速度很快呢?」,我直接大聲說「索引底層數據結構是B樹,查詢的時候用二分查找」,結果整個大房間就我一我的聲音,全部同事都看過來,場面有點尷尬。node
上一篇文章分析樹基本概念、名詞解釋、樹三種遍歷方式,今天繼續來看二叉樹各類名詞機率,看這些名詞概念,確定是各類不爽,大概瀏覽下知道怎麼回事就ok。程序員
一:概念,下面這些內容摘自維基百科面試
在計算機科學中,二叉樹是每一個節點最多有兩個子樹的樹結構。一般子樹被稱做「左子樹」(left subtree)和「右子樹」(right subtree)。二叉樹常被用於實現二叉查找樹和二叉堆。算法
二叉樹的每一個結點至多隻有二棵子樹(不存在度大於2的結點),二叉樹的子樹有左右之分,次序不能顛倒。二叉樹的第i層至多有個結點;深度爲k的二叉樹至多有個結點;對任何一棵二叉樹T,若是其終端結點數爲,度爲2的結點數爲,則。sql
樹和二叉樹的三個主要差異:數據庫
樹的結點個數至少爲1,而二叉樹的結點個數能夠爲0;編程
樹中結點的最大度數沒有限制,而二叉樹結點的最大度數爲2;數據結構
樹的結點無左、右之分,而二叉樹的結點有左、右之分。app
徹底二叉樹和滿二叉樹
滿二叉樹:一棵深度爲k,且有個節點成爲滿二叉樹
徹底二叉樹:深度爲k,有n個節點的二叉樹,當且僅當其每個節點都與深度爲k的滿二叉樹中序號爲1至n的節點對應時,稱之爲徹底二叉樹
二:示例圖
三:動畫圖
四:代碼分析
一、是否爲空
1 /** 2 * 若樹爲空,則返回true;不然返回false 3 */ 4 bool IsEmpty() { 5 return root == 0; 6 }
二、計算樹的深度
1 /** 2 * 以傳入節點爲基礎計算樹的深度 3 */ 4 int GetTreeDept(const TNode<T> *t) { 5 int i, j; 6 if (t == 0) { 7 return 0; 8 } else { 9 //遞歸計算左子樹的深度 10 i = this->GetTreeDept(t->lchild); 11 //遞歸計算右子樹的深度 12 j = this->GetTreeDept(t->rchild); 13 } 14 15 //t的深度爲其左右子樹中深度中的大者加1 16 return i > j ? i + 1 : j + 1; 17 }
三、清空樹的節點
1 /** 2 *清空樹的全部節點 3 */ 4 void Clear() { 5 Clear(root); 6 } 7 8 /** 9 *根據節點遞歸清空樹 10 */ 11 void Clear(TNode<T>* t) { 12 //判斷指針是否爲空 13 if (t) { 14 //獲取資源當即放入管理對象(參考Effective C++裏邊條款13) 15 //tr1::shared_ptr(引用計數智慧指針)管理對象比auto_ptr更強大 16 std::auto_ptr<TNode<T> > new_ptr(t); 17 18 //遞歸清空右子樹 19 Clear(new_ptr->rchild); 20 21 //遞歸清空左子樹 22 Clear(new_ptr->lchild); 23 } 24 25 //清空樹的根節點 26 t = 0; 27 }
四、獲取樹最大節點和最小節點
1 /** 2 *獲取樹的最大節點 3 */ 4 TNode<T>* GetMax(TNode<T>* t) const { 5 //判斷數節點是否爲空 6 if (t) { 7 //根據二叉樹特性,最大值必定在右子樹; 8 //循環右子樹,直到葉子節點 9 while (t->rchild) { 10 //指向下一個節點 11 t = t->rchild; 12 } 13 } 14 //返回找到最大節點 15 return t; 16 } 17 /** 18 *獲取樹的最小節點 19 */ 20 TNode<T>* GetMin(TNode<T>* t) const { 21 //判斷數節點是否爲空 22 if (t) { 23 //根據二叉樹特性,最大值必定在左子樹; 24 //循環左子樹,直到葉子節點 25 while (t->lchild) { 26 //指向下一個節點 27 t = t->lchild; 28 } 29 } 30 //返回找到最小節點 31 return t; 32 } 33 /** 34 *根據模式類型查找樹的最大值或者最小值 35 */ 36 TNode<T>* GetNode(Mode mode) const { 37 //t指向根節點 38 TNode<T>* t = root; 39 //判斷數節點是否爲空 40 if (t) { 41 if (mode == Min) { 42 //根據二叉樹特性,最大值必定在左子樹; 43 //循環左子樹,直到葉子節點 44 while (t->lchild) { 45 //指向左子樹下一個節點 46 t = t->lchild; 47 } 48 } else if (mode == Max) { 49 //根據二叉樹特性,最大值必定在右子樹; 50 //循環右子樹,直到葉子節點 51 while (t->rchild) { 52 //指向右子樹下一個節點 53 t = t->rchild; 54 } 55 } 56 } 57 //返回找到節點 58 return t; 59 }
五、獲取傳入節點父節點
1 /** 2 *獲取傳入的節點從傳入樹p中找到它的父節點 3 */ 4 TNode<T>* GetParentNode(TNode<T> *p, const T &value,TNode<T> *returnValue) { 5 //p節點存在而且傳入的值不是根節點值 6 if (p && p->data == value) { 7 return 0; 8 } 9 10 //用二分查找定位值value所在節點 11 TNode<T> *t = this->SearchTree(p, value); 12 13 //判斷t和p都不爲空 14 if (t && p) { 15 //若是value的節點等於p節點左孩子或者右孩子,p就是value的節點父親 16 if (p->lchild == t || p->rchild == t) { 17 //賦值p節點給返回值變量 18 returnValue = p; 19 } else if (value > p->data) {//若是value只大於p節點值,則遞歸右孩子 20 //直到找到value的父節點複製給返回值變量 21 returnValue = GetParentNode(p->rchild, value,returnValue); 22 } else {////若是value只小於p節點值,則遞歸左孩子 23 //直到找到value的父節點複製給返回值變量 24 returnValue = GetParentNode(p->lchild, value,returnValue); 25 } 26 } 27 28 return returnValue; 29 30 } 31 32 /** 33 *獲取傳入的節點的父節點 34 */ 35 TNode<T>* GetParentNode(const T &value,TNode<T> *returnValue) { 36 return this->GetParentNode(root, value,returnValue); 37 }
六、二分查找
代碼分析:
1 /** 2 * 在以T爲根節點的樹中搜索值爲value的節點 3 */ 4 TNode<T>* SearchTree(TNode<T>* &t, const T &value) { 5 //判斷t節點是否爲空 6 while (t) { 7 //若是節點值等於value,則代表已經找到目標節點 8 if (t->data == value) { 9 return t; 10 } else if (value > t->data) {//若是value大於t節點值,則遞歸查詢右子樹 11 return SearchTree(t->rchild, value); 12 } else {//若是value小於t節點值,則遞歸查詢左子樹 13 return SearchTree(t->lchild, value); 14 } 15 } 16 return t; 17 }
動畫演示:
七、插入節點
代碼分析:
1 /** 2 *插入一個節點到目標樹中 3 */ 4 void Insert(const T &value, TNode<T>* &t) { 5 //若是目標樹爲空,則新new一個根節點 6 if (t == 0) { 7 //新建立一個節點,並把value設置爲節點值 8 t = new TNode<T>(value); 9 } else if (value < t->data) {//若是value值小於t節點值 10 //遞歸左子樹插入函數,直到找到節點插入 11 this->Insert(value, t->lchild); 12 } else if (value > t->data) {//若是value值大於t節點值 13 //遞歸右子樹插入函數,直到找到節點插入 14 this->Insert(value, t->rchild); 15 } 16 } 17 /** 18 *插入一個節點到根節點爲root的樹中 19 */ 20 void Insert(const T &value) { 21 this->Insert(value, root); 22 }
動畫演示
八、刪除節點
代碼分析:
1 /** 2 *根據節點值刪除節點信息 3 */ 4 void Delete(const T &value) { 5 Delete(value, root); 6 } 7 /** 8 *根據節點值刪除以傳入t爲根節點樹節點信息 9 */ 10 void Delete(const T &value, TNode<T>* &t) { 11 //判斷是否t爲空null 12 if (t) { 13 //經過二分查找定位value所在的節點 14 TNode<T> *p = this->SearchTree(t, value); 15 //中間變量,用於待刪除節點左右子樹都不爲空的狀況下 16 TNode<T> *q = p; 17 if (p) { 18 //若是p節點的左右孩子都不爲空,則根據二叉樹定義 19 //必須在子右子樹中找到最新節點做爲新節點 20 //當左右子樹都爲空狀況下,右子樹最小節點就是樹(中序遍歷)節點的後繼節點 21 if (p->lchild != 0 && p->rchild != 0) { 22 //獲取右子樹中最小的節點 23 q = this->GetMin(p->rchild); 24 } 25 //獲取資源當即放入管理對象(參考Effective C++裏邊條款13) 26 //tr1::shared_ptr(引用計數智慧指針)管理對象比auto_ptr更強大 27 //若是p節點的左右子樹都不爲空,則釋放p節點子右子樹的最小節點 28 //改變p節點的值便可 29 auto_ptr<TNode<T> > new_ptr(q); 30 31 TNode<T> *parent = 0; 32 TNode<T> *returnValue; 33 //刪除葉子節點(節點左右孩子都爲空) 34 if (p->lchild == 0 && p->rchild == 0) { 35 //若是p節點和傳入的根節點相等 36 if (t == p) { 37 //直接設置t爲空 38 t = 0; 39 } else { 40 //獲取p節點的父節點 41 parent = this->GetParentNode(t, p->data,returnValue); 42 43 //若是父節點的左孩子等於p節點 44 if (parent->lchild == p) { 45 //設置父節點的左孩子等於空 46 parent->lchild = 0; 47 } else {//若是父節點的右孩子等於p節點 48 //設置父節點的右孩子等於空 49 parent->rchild = 0; 50 } 51 } 52 53 } else if (p->rchild == 0) {//刪除節點p右孩子爲空,左孩子有節點 54 //若是p節點和傳入的根節點相等 55 if (t == p) { 56 //直接設置t節點等於左孩子 57 t = t->lchild; 58 } else { 59 //獲取p節點的父節點 60 parent = this->GetParentNode(t, p->data,returnValue); 61 //若是父節點的左孩子等於p節點 62 if (parent->lchild == p) { 63 //設置父節點左孩子等於p節點左孩子 64 parent->lchild = p->lchild; 65 } else {//若是父節點的右孩子等於p節點 66 //設置父節點右孩子等於p節點左孩子 67 parent->rchild = p->lchild; 68 } 69 } 70 71 } else if (p->lchild == 0) {//刪除節點p左孩子爲空,右孩子有節點 72 //若是p節點和傳入的根節點相等 73 if (t == p) { 74 //直接設置t節點等於右孩子 75 t = t->rchild; 76 } else { 77 //獲取p節點的父節點 78 parent = this->GetParentNode(t, p->data,returnValue); 79 //若是父節點的右孩子等於p節點 80 if (parent->rchild == p) { 81 //設置父節點右孩子等於p節點右孩子 82 parent->rchild = p->rchild; 83 } else {//若是父節點的左孩子等於p節點 84 //設置父節點右孩子等於p節點右孩子 85 parent->lchild = p->rchild; 86 } 87 } 88 } else {//刪除節點p左右都有孩子 89 //獲取q節點的父節點 90 parent = this->GetParentNode(t,q->data,returnValue); 91 //設置p節點值等於q節點值 92 p->data = q->data; 93 //若是q的父節點等於p 94 if (parent == p) { 95 //設置q節點的父節點右孩子爲q節點右孩子 96 parent->rchild = q->rchild; 97 } else {// 98 //設置q節點的父節點左孩子爲q節點右孩子 99 parent->lchild = q->rchild; 100 } 101 102 } 103 } 104 } 105 }
動畫演示
九、獲取目標節點後繼節點(中序遍歷)
1 /** 2 *在傳入p的樹中找出節點值爲value的後繼節點方法 3 */ 4 TNode<T>* TreeSuccessor(TNode<T> *p,const T &value,TNode<T> *returnValue){ 5 //若是t節點非空 6 if(p){ 7 //傳入p樹和節點值value找到對應的節點 8 TNode<T> *t = this->SearchTree(p, value); 9 //若是節點右子樹不爲空 10 if(t->rchild != 0){ 11 //直接獲取右子樹中最小節點,便是節點的後繼節點 12 returnValue = this->GetMin(t->rchild); 13 }else{ 14 //獲取目標節點父節點 15 TNode<T> *parent = this->GetParentNode(t->data,returnValue); 16 17 //若是父節點不爲空而且t節點等於父節點的右節點,這一個文字不太好描述,請參照圖 18 while(parent && t == parent->rchild){ 19 //父節點賦值給t節點 20 t = parent; 21 //獲取父節點的父節點(目標節點爺爺) 22 parent = this->GetParentNode(parent->data,returnValue); 23 } 24 //找到後繼節點賦值給返回變量 25 returnValue = parent; 26 } 27 } 28 29 return returnValue; 30 } 31 /** 32 *在以root爲根節點中找出節點值爲value的後繼節點方法 33 */ 34 TNode<T>* TreeSuccessor(const T &value,TNode<T> *returnValue){ 35 return TreeSuccessor(root,value,returnValue); 36 }
以下圖:
十、先序、中序、後序遞歸和非的遞歸遍歷,更詳細的請參考上一篇文章,爲何在這裏有展現一遍,我堅信在複雜的東西,多動手寫幾回都能很好的理解
1 /** 2 *前序非遞歸(利用棧)遍歷二叉樹 3 *前序遍歷的規則:根左右 4 *非遞歸遍歷樹會常常當作面試題,考察面試者的編程能力 5 *防止下次被鄙視,應該深刻理解而且動手在紙寫出來 6 */ 7 void PreOrderTraverse() { 8 //申明一個棧對象 9 stack<TNode<T>*> s; 10 //t首先指向根節點 11 TNode<T> *t = root; 12 //壓入一個空指針,做爲判斷條件 13 s.push(0); 14 15 //若是t所值節點非空 16 while (t != 0) { 17 //直接訪問根節點 18 std::cout << (&t->data) << " "; 19 20 //右孩子指針爲非空 21 if (t->rchild != 0) { 22 //入棧右孩子指針 23 s.push(t->rchild); 24 } 25 26 //左孩子指針爲非空 27 if (t->lchild != 0) { 28 //直接指向其左孩子 29 t = t->lchild; 30 } else {//左孩子指針爲空 31 //獲取棧頂元素(右孩子指針) 32 t = s.top(); 33 //清楚棧頂元素 34 s.pop(); 35 } 36 37 } 38 } 39 40 /** 41 *中序非遞歸(利用棧)遍歷二叉樹 42 *前序遍歷的規則:左根右 43 */ 44 void InOrderTraverse() { 45 //申明一個棧對象 46 stack<TNode<T>*> s; 47 //t首先指向根節點 48 TNode<T>* t = root; 49 50 //節點不爲空或者棧對象不爲空,都進入循環 51 while (t != 0 || !s.empty()) { 52 //若是t節點非空 53 if (t) { 54 //入棧t節點 55 s.push(t); 56 //t節點指向其左孩子 57 t = t->lchild; 58 } else { 59 //獲取棧頂元素(左孩子指針) 60 t = s.top(); 61 //清楚棧頂元素 62 s.pop(); 63 //直接訪問t節點 64 std::cout << (&t->data) << " "; 65 //t節點指向其右孩子 66 t = t->rchild; 67 } 68 } 69 } 70 /** 71 *後序非遞歸(利用棧)遍歷二叉樹 72 *前序遍歷的規則:左右根 73 */ 74 void PostOrderTraverse() { 75 //申明一個棧對象 76 stack<TNode<T>*> s; 77 //t首先指向根節點 78 TNode<T>* t = root; 79 //申請中間變量,用作判斷標識 80 TNode<T>* r; 81 //節點不爲空或者棧對象不爲空,都進入循環 82 while (t != 0 || !s.empty()) { 83 //若是t節點非空 84 if (t) { 85 //入棧t節點 86 s.push(t); 87 //t節點指向其左孩子 88 t = t->lchild; 89 } else { 90 //獲取棧頂元素(左孩子指針) 91 t = s.top(); 92 //判斷t的右子樹是否存在而且沒有訪問過 93 if (t->rchild && t->rchild != r) { 94 //t節點指向其右孩子 95 t = t->rchild; 96 //入棧t節點 97 s.push(t); 98 //t節點指向其左孩子 99 t = t->lchild; 100 } else { 101 //獲取棧頂元素(左孩子指針) 102 t = s.top(); 103 //清楚棧頂元素 104 s.pop(); 105 //直接訪問t節點 106 std::cout << (&t->data) << " "; 107 //設置已經訪問過的節點,防止屢次訪問(右孩子指針) 108 r = t; 109 t = 0; 110 } 111 } 112 } 113 } 114 /** 115 * 根據模式遞歸遍歷二叉樹 116 * 下面代碼相對比較簡單,只要記住遍歷樹的規則而且弄一點遞歸,均可以寫出來 117 * 若是在面試中實在寫不出來非遞歸方式,能夠寫一個遞歸版本,也許能夠爭取一個好的工做 118 */ 119 void OrderTraverse(const TNode<T>* t, Style mode) { 120 if (t) { 121 //先序遍歷二叉樹:根左右 122 if (mode == Pre) { 123 //直接訪問t節點 124 std::cout << (&t->data) << " "; 125 //遞歸遍歷左子樹 126 this->OrderTraverse(t->lchild, mode); 127 //遞歸遍歷右子樹 128 this->OrderTraverse(t->rchild, mode); 129 } 130 //中序遍歷二叉樹:左根右 131 if (mode == In) { 132 //遞歸遍歷左子樹 133 this->OrderTraverse(t->lchild, mode); 134 //直接訪問t節點 135 std::cout << (&t->data) << " "; 136 //遞歸遍歷右子樹 137 this->OrderTraverse(t->rchild, mode); 138 } 139 //後序遍歷二叉樹:左右根 140 if (mode == Post) { 141 //遞歸遍歷左子樹 142 this->OrderTraverse(t->lchild, mode); 143 //遞歸遍歷右子樹 144 this->OrderTraverse(t->rchild, mode); 145 //直接訪問t節點 146 std::cout << (&t->data) << " "; 147 } 148 } 149 }
十一、按層級遍歷樹
1 /** 2 *按層級從左到右遍歷樹 3 */ 4 void LevelOrderTraverse(){ 5 //聲明一個隊列隊形 6 queue<TNode<T>* > q; 7 //聲明變量a,t;並把root賦值給t 8 TNode<T> *a,*t = root; 9 //若t節點非空 10 if(t){ 11 //t節點入隊列 12 q.push(t); 13 //若是隊列不爲空 14 while(!q.empty()){ 15 //獲取隊列頭結點 16 a = q.front(); 17 //數據隊形出隊列 18 q.pop(); 19 //直接訪問隊列頭結點值 20 std::cout<<a->data<<" "; 21 //若a節點左子樹不爲空 22 if(a->lchild){ 23 //左子樹入隊列q 24 q.push(a->lchild); 25 } 26 //若a節點右子樹不爲空 27 if(a->rchild){ 28 //右子樹入隊列q 29 q.push(a->rchild); 30 } 31 } 32 } 33 }
十二、運行結果,因爲在虛擬機中打中文,實在太痛苦,弄一點英文裝下B
測試代碼以下:
1 void test() { 2 Insert(5); 3 Insert(3); 4 Insert(4); 5 Insert(6); 6 Insert(2); 7 Insert(1); 8 Insert(10); 9 Insert(9); 10 Insert(8); 11 Insert(11); 12 Insert(12); 13 std::cout << "create tree success" << std::endl; 14 15 std::cout << "create tree after is null ? "; 16 std::cout << boolalpha << this->IsEmpty(); 17 18 std::cout << std::endl; 19 std::cout << "calculated depth of the tree begins" << std::endl; 20 std::cout << "tree is dept = " << this->GetTreeDept(this->GetRoot()); 21 std::cout << std::endl; 22 std::cout << "calculated depth of the tree end" << std::endl; 23 24 std::cout << std::endl; 25 std::cout << "recursion--------------------begin" << std::endl; 26 std::cout << "pre order traverse:"; 27 OrderTraverse(GetRoot(), Pre); 28 std::cout << endl; 29 30 std::cout << "in order traverse:"; 31 OrderTraverse(GetRoot(), In); 32 std::cout << endl; 33 34 std::cout << "post order traverse:"; 35 OrderTraverse(GetRoot(), Post); 36 std::cout << endl; 37 std::cout << "recursion--------------------end" << std::endl; 38 39 std::cout << "get parent node--------------begin" << std::endl; 40 TNode<T> *returnValue; 41 TNode<T> *node = GetParentNode(5,returnValue); 42 if (node) { 43 std::cout << "node=" << node->data; 44 } 45 std::cout << "get parent node--------------end" << std::endl; 46 std::cout << "delete-----------------------begin" << std::endl; 47 Delete(5); 48 std::cout << "delete-----------------------end" << std::endl; 49 50 std::cout << "recursion--------------------begin" << std::endl; 51 std::cout << "in order traverse:"; 52 OrderTraverse(GetRoot(), In); 53 std::cout << endl; 54 std::cout << "recursion--------------------end" << std::endl; 55 std::cout<<"tree max:"<<GetMax(GetRoot())->data<<std::endl; 56 std::cout<<"tree min:"<<GetMin(GetRoot())->data<<std::endl; 57 }
結果以下圖
十一、完整代碼
TNode.h
1 /* 2 * TLNode.h 3 * 4 * Created on: 2013-7-6 5 * Author: sunysen 6 */ 7 8 #ifndef TLNODE_H_ 9 #define TLNODE_H_ 10 template <class T> 11 class TNode{ 12 public: 13 T data; 14 TNode *rchild,*lchild; 15 TNode(T value):data(value),rchild(0),lchild(0){} 16 }; 17 18 #endif /* TLNODE_H_ */
BSTree.h
1 /* 2 * LinkTree.h 3 * Created on: 2013-7-6 4 * Author: sunysen 5 */ 6 7 #ifndef BSTREE_H_ 8 #define BSTREE_H_ 9 #include "core/common/Common.h" 10 #include "core/node/TNode.h" 11 //獲取樹最大最小值模式 12 enum Mode { 13 Max, Min 14 }; 15 //樹的三種遍歷方式 16 enum ORDER_MODE { 17 Pre, In, Post 18 }; 19 template<class T> 20 class BSTree { 21 private: 22 TNode<T> *root;//樹的根節點 23 public: 24 /** 25 * 構造函數初始化樹根節點 26 */ 27 BSTree() : 28 root(0) { 29 } 30 /** 31 *析構行數釋放全部構造的資源 32 */ 33 ~BSTree(){ 34 Clear(); 35 } 36 /** 37 * 若樹爲空,則返回true;不然返回false 38 */ 39 bool IsEmpty() { 40 return root == 0; 41 } 42 43 /** 44 * 以傳入節點爲基礎計算樹的深度 45 */ 46 int GetTreeDept(const TNode<T> *t) { 47 int i, j; 48 if (t == 0) { 49 return 0; 50 } else { 51 //遞歸計算左子樹的深度 52 i = this->GetTreeDept(t->lchild); 53 //遞歸計算右子樹的深度 54 j = this->GetTreeDept(t->rchild); 55 } 56 57 //t的深度爲其左右子樹中深度中的大者加1 58 return i > j ? i + 1 : j + 1; 59 } 60 /** 61 *清空樹的全部節點 62 */ 63 void Clear() { 64 Clear(root); 65 } 66 67 /** 68 *根據節點遞歸清空樹 69 */ 70 void Clear(TNode<T>* t) { 71 //判斷指針是否爲空 72 if (t) { 73 //獲取資源當即放入管理對象(參考Effective C++裏邊條款13) 74 //tr1::shared_ptr(引用計數智慧指針)管理對象比auto_ptr更強大 75 std::auto_ptr<TNode<T> > new_ptr(t); 76 77 //遞歸清空右子樹 78 Clear(new_ptr->rchild); 79 80 //遞歸清空左子樹 81 Clear(new_ptr->lchild); 82 } 83 84 //清空樹的根節點 85 t = 0; 86 } 87 88 /** 89 *獲取樹的最大節點 90 */ 91 TNode<T>* GetMax(TNode<T>* t) const { 92 //判斷數節點是否爲空 93 if (t) { 94 //根據二叉樹特性,最大值必定在右子樹; 95 //循環右子樹,直到葉子節點 96 while (t->rchild) { 97 //指向下一個節點 98 t = t->rchild; 99 } 100 } 101 //返回找到最大節點 102 return t; 103 } 104 /** 105 *獲取樹的最小節點 106 */ 107 TNode<T>* GetMin(TNode<T>* t) const { 108 //判斷數節點是否爲空 109 if (t) { 110 //根據二叉樹特性,最大值必定在左子樹; 111 //循環左子樹,直到葉子節點 112 while (t->lchild) { 113 //指向下一個節點 114 t = t->lchild; 115 } 116 } 117 //返回找到最小節點 118 return t; 119 } 120 /** 121 *根據模式類型查找樹的最大值或者最小值 122 */ 123 TNode<T>* GetNode(Mode mode) const { 124 //t指向根節點 125 TNode<T>* t = root; 126 //判斷數節點是否爲空 127 if (t) { 128 if (mode == Min) { 129 //根據二叉樹特性,最大值必定在左子樹; 130 //循環左子樹,直到葉子節點 131 while (t->lchild) { 132 //指向左子樹下一個節點 133 t = t->lchild; 134 } 135 } else if (mode == Max) { 136 //根據二叉樹特性,最大值必定在右子樹; 137 //循環右子樹,直到葉子節點 138 while (t->rchild) { 139 //指向右子樹下一個節點 140 t = t->rchild; 141 } 142 } 143 } 144 //返回找到節點 145 return t; 146 } 147 /** 148 *獲取傳入的節點從傳入樹p中找到它的父節點 149 */ 150 TNode<T>* GetParentNode(TNode<T> *p, const T &value,TNode<T> *returnValue) { 151 //p節點存在而且傳入的值不是根節點值 152 if (p && p->data == value) { 153 return 0; 154 } 155 156 //用二分查找定位值value所在節點 157 TNode<T> *t = this->SearchTree(p, value); 158 159 //判斷t和p都不爲空 160 if (t && p) { 161 //若是value的節點等於p節點左孩子或者右孩子,p就是value的節點父親 162 if (p->lchild == t || p->rchild == t) { 163 //賦值p節點給返回值變量 164 returnValue = p; 165 } else if (value > p->data) {//若是value只大於p節點值,則遞歸右孩子 166 //直到找到value的父節點複製給返回值變量 167 returnValue = GetParentNode(p->rchild, value,returnValue); 168 } else {////若是value只小於p節點值,則遞歸左孩子 169 //直到找到value的父節點複製給返回值變量 170 returnValue = GetParentNode(p->lchild, value,returnValue); 171 } 172 } 173 174 return returnValue; 175 176 } 177 178 /** 179 *獲取傳入的節點的父節點 180 */ 181 TNode<T>* GetParentNode(const T &value,TNode<T> *returnValue) { 182 return this->GetParentNode(root, value,returnValue); 183 } 184 185 /** 186 * 在以T爲根節點的樹中搜索值爲value的節點 187 */ 188 TNode<T>* SearchTree(TNode<T>* &t, const T &value) { 189 //判斷t節點是否爲空 190 while (t) { 191 //若是節點值等於value,則代表已經找到目標節點 192 if (t->data == value) { 193 return t; 194 } else if (value > t->data) {//若是value大於t節點值,則遞歸查詢右子樹 195 return SearchTree(t->rchild, value); 196 } else {//若是value小於t節點值,則遞歸查詢左子樹 197 return SearchTree(t->lchild, value); 198 } 199 } 200 return t; 201 } 202 203 /** 204 *插入一個節點到目標樹中 205 */ 206 void Insert(const T &value, TNode<T>* &t) { 207 //若是目標樹爲空,則新new一個根節點 208 if (t == 0) { 209 //新建立一個節點,並把value設置爲節點值 210 t = new TNode<T>(value); 211 } else if (value < t->data) {//若是value值小於t節點值 212 //遞歸左子樹插入函數,直到找到節點插入 213 this->Insert(value, t->lchild); 214 } else if (value > t->data) {//若是value值大於t節點值 215 //遞歸右子樹插入函數,直到找到節點插入 216 this->Insert(value, t->rchild); 217 } 218 } 219 /** 220 *插入一個節點到根節點爲root的樹中 221 */ 222 void Insert(const T &value) { 223 this->Insert(value, root); 224 } 225 226 /** 227 *根據節點值刪除節點信息 228 */ 229 void Delete(const T &value) { 230 Delete(value, root); 231 } 232 /** 233 *根據節點值刪除以傳入t爲根節點樹節點信息 234 */ 235 void Delete(const T &value, TNode<T>* &t) { 236 //判斷是否t爲空null 237 if (t) { 238 //經過二分查找定位value所在的節點 239 TNode<T> *p = this->SearchTree(t, value); 240 //中間變量,用於待刪除節點左右子樹都不爲空的狀況下 241 TNode<T> *q = p; 242 if (p) { 243 //若是p節點的左右孩子都不爲空,則根據二叉樹定義 244 //必須在子右子樹中找到最新節點做爲新節點 245 if (p->lchild != 0 && p->rchild != 0) { 246 //獲取右子樹中最小的節點 247 q = this->GetMin(p->rchild); 248 } 249 //獲取資源當即放入管理對象(參考Effective C++裏邊條款13) 250 //tr1::shared_ptr(引用計數智慧指針)管理對象比auto_ptr更強大 251 //若是p節點的左右子樹都不爲空,則釋放p節點子右子樹的最小節點 252 //改變p節點的值,便可刪除節點 253 auto_ptr<TNode<T> > new_ptr(q); 254 255 TNode<T> *parent = 0; 256 TNode<T> *returnValue; 257 //刪除葉子節點(節點左右孩子都爲空) 258 if (p->lchild == 0 && p->rchild == 0) { 259 //若是p節點和傳入的根節點相等 260 if (t == p) { 261 //直接設置t爲空 262 t = 0; 263 } else { 264 //獲取p節點的父節點 265 parent = this->GetParentNode(t, p->data,returnValue); 266 267 //若是父節點的左孩子等於p節點 268 if (parent->lchild == p) { 269 //設置父節點的左孩子等於空 270 parent->lchild = 0; 271 } else {//若是父節點的右孩子等於p節點 272 //設置父節點的右孩子等於空 273 parent->rchild = 0; 274 } 275 } 276 277 } else if (p->rchild == 0) {//刪除節點p右孩子爲空,左孩子有節點 278 //若是p節點和傳入的根節點相等 279 if (t == p) { 280 //直接設置t節點等於左孩子 281 t = t->lchild; 282 } else { 283 //獲取p節點的父節點 284 parent = this->GetParentNode(t, p->data,returnValue); 285 //若是父節點的左孩子等於p節點 286 if (parent->lchild == p) { 287 //設置父節點左孩子等於p節點左孩子 288 parent->lchild = p->lchild; 289 } else {//若是父節點的右孩子等於p節點 290 //設置父節點右孩子等於p節點左孩子 291 parent->rchild = p->lchild; 292 } 293 } 294 295 } else if (p->lchild == 0) {//刪除節點p左孩子爲空,右孩子有節點 296 //若是p節點和傳入的根節點相等 297 if (t == p) { 298 //直接設置t節點等於右孩子 299 t = t->rchild; 300 } else { 301 //獲取p節點的父節點 302 parent = this->GetParentNode(t, p->data,returnValue); 303 //若是父節點的右孩子等於p節點 304 if (parent->rchild == p) { 305 //設置父節點右孩子等於p節點右孩子 306 parent->rchild = p->rchild; 307 } else {//若是父節點的左孩子等於p節點 308 //設置父節點右孩子等於p節點右孩子 309 parent->lchild = p->rchild; 310 } 311 } 312 } else {//刪除節點p左右都有孩子 313 //獲取q節點的父節點 314 parent = this->GetParentNode(t,q->data,returnValue); 315 //設置p節點值等於q節點值 316 p->data = q->data; 317 //若是q的父節點等於p 318 if (parent == p) { 319 //設置q節點的父節點右孩子爲q節點右孩子 320 parent->rchild = q->rchild; 321 } else {// 322 //設置q節點的父節點左孩子爲q節點右孩子 323 parent->lchild = q->rchild; 324 } 325 326 } 327 } 328 } 329 } 330 331 /** 332 *前序非遞歸(利用棧)遍歷二叉樹 333 *前序遍歷的規則:根左右 334 *非遞歸遍歷樹會常常當作面試題,考察面試者的編程能力 335 *防止下次被鄙視,應該深刻理解而且動手在紙寫出來 336 */ 337 void PreOrderTraverse() { 338 //申明一個棧對象 339 stack<TNode<T>*> s; 340 //t首先指向根節點 341 TNode<T> *t = root; 342 //壓入一個空指針,做爲判斷條件 343 s.push(0); 344 345 //若是t所值節點非空 346 while (t != 0) { 347 //直接訪問根節點 348 std::cout << (&t->data) << " "; 349 350 //右孩子指針爲非空 351 if (t->rchild != 0) { 352 //入棧右孩子指針 353 s.push(t->rchild); 354 } 355 356 //左孩子指針爲非空 357 if (t->lchild != 0) { 358 //直接指向其左孩子 359 t = t->lchild; 360 } else {//左孩子指針爲空 361 //獲取棧頂元素(右孩子指針) 362 t = s.top(); 363 //清楚棧頂元素 364 s.pop(); 365 } 366 367 } 368 } 369 370 /** 371 *中序非遞歸(利用棧)遍歷二叉樹 372 *前序遍歷的規則:左根右 373 */ 374 void InOrderTraverse() { 375 //申明一個棧對象 376 stack<TNode<T>*> s; 377 //t首先指向根節點 378 TNode<T>* t = root; 379 380 //節點不爲空或者棧對象不爲空,都進入循環 381 while (t != 0 || !s.empty()) { 382 //若是t節點非空 383 if (t) { 384 //入棧t節點 385 s.push(t); 386 //t節點指向其左孩子 387 t = t->lchild; 388 } else { 389 //獲取棧頂元素(左孩子指針) 390 t = s.top(); 391 //清楚棧頂元素 392 s.pop(); 393 //直接訪問t節點 394 std::cout << (&t->data) << " "; 395 //t節點指向其右孩子 396 t = t->rchild; 397 } 398 } 399 } 400 /** 401 *後序非遞歸(利用棧)遍歷二叉樹 402 *前序遍歷的規則:左右根 403 */ 404 void PostOrderTraverse() { 405 //申明一個棧對象 406 stack<TNode<T>*> s; 407 //t首先指向根節點 408 TNode<T>* t = root; 409 //申請中間變量,用作判斷標識 410 TNode<T>* r; 411 //節點不爲空或者棧對象不爲空,都進入循環 412 while (t != 0 || !s.empty()) { 413 //若是t節點非空 414 if (t) { 415 //入棧t節點 416 s.push(t); 417 //t節點指向其左孩子 418 t = t->lchild; 419 } else { 420 //獲取棧頂元素(左孩子指針) 421 t = s.top(); 422 //判斷t的右子樹是否存在而且沒有訪問過 423 if (t->rchild && t->rchild != r) { 424 //t節點指向其右孩子 425 t = t->rchild; 426 //入棧t節點 427 s.push(t); 428 //t節點指向其左孩子 429 t = t->lchild; 430 } else { 431 //獲取棧頂元素(左孩子指針) 432 t = s.top(); 433 //清楚棧頂元素 434 s.pop(); 435 //直接訪問t節點 436 std::cout << (&t->data) << " "; 437 //設置已經訪問過的節點,防止屢次訪問(右孩子指針) 438 r = t; 439 t = 0; 440 } 441 } 442 } 443 } 444 /** 445 * 根據模式遞歸遍歷二叉樹 446 * 下面代碼相對比較簡單,只要記住遍歷樹的規則而且弄一點遞歸,均可以寫出來 447 * 若是在面試中實在寫不出來非遞歸方式,能夠寫一個遞歸版本,也許能夠爭取一個好的工做 448 */ 449 void OrderTraverse(const TNode<T>* t, Style mode) { 450 if (t) { 451 //先序遍歷二叉樹:根左右 452 if (mode == Pre) { 453 //直接訪問t節點 454 std::cout << (&t->data) << " "; 455 //遞歸遍歷左子樹 456 this->OrderTraverse(t->lchild, mode); 457 //遞歸遍歷右子樹 458 this->OrderTraverse(t->rchild, mode); 459 } 460 //中序遍歷二叉樹:左根右 461 if (mode == In) { 462 //遞歸遍歷左子樹 463 this->OrderTraverse(t->lchild, mode); 464 //直接訪問t節點 465 std::cout << (&t->data) << " "; 466 //遞歸遍歷右子樹 467 this->OrderTraverse(t->rchild, mode); 468 } 469 //後序遍歷二叉樹:左右根 470 if (mode == Post) { 471 //遞歸遍歷左子樹 472 this->OrderTraverse(t->lchild, mode); 473 //遞歸遍歷右子樹 474 this->OrderTraverse(t->rchild, mode); 475 //直接訪問t節點 476 std::cout << (&t->data) << " "; 477 } 478 } 479 } 480 481 482 TNode<T>* GetRoot() { 483 return root; 484 } 485 486 void test() { 487 Insert(5); 488 Insert(3); 489 Insert(4); 490 Insert(6); 491 Insert(2); 492 Insert(1); 493 Insert(10); 494 Insert(9); 495 Insert(8); 496 Insert(11); 497 Insert(12); 498 std::cout << "create tree success" << std::endl; 499 500 std::cout << "create tree after is null ? "; 501 std::cout << boolalpha << this->IsEmpty(); 502 503 std::cout << std::endl; 504 std::cout << "calculated depth of the tree begins" << std::endl; 505 std::cout << "tree is dept = " << this->GetTreeDept(this->GetRoot()); 506 std::cout << std::endl; 507 std::cout << "calculated depth of the tree end" << std::endl; 508 509 std::cout << std::endl; 510 std::cout << "recursion--------------------begin" << std::endl; 511 std::cout << "pre order traverse:"; 512 OrderTraverse(GetRoot(), Pre); 513 std::cout << endl; 514 515 std::cout << "in order traverse:"; 516 OrderTraverse(GetRoot(), In); 517 std::cout << endl; 518 519 std::cout << "post order traverse:"; 520 OrderTraverse(GetRoot(), Post); 521 std::cout << endl; 522 std::cout << "recursion--------------------end" << std::endl; 523 524 std::cout << "get parent node--------------begin" << std::endl; 525 TNode<T> *returnValue; 526 TNode<T> *node = GetParentNode(5,returnValue); 527 if (node) { 528 std::cout << "node=" << node->data; 529 } 530 std::cout << "get parent node--------------end" << std::endl; 531 std::cout << "delete-----------------------begin" << std::endl; 532 Delete(5); 533 std::cout << "delete-----------------------end" << std::endl; 534 535 std::cout << "recursion--------------------begin" << std::endl; 536 std::cout << "in order traverse:"; 537 OrderTraverse(GetRoot(), In); 538 std::cout << endl; 539 std::cout << "recursion--------------------end" << std::endl; 540 std::cout<<"tree max:"<<GetMax(GetRoot())->data<<std::endl; 541 std::cout<<"tree min:"<<GetMin(GetRoot())->data<<std::endl; 542 } 543 544 }; 545 546 #endif /* BSTREE_H_ */
五:環境
一、運行環境:Ubuntu 10.04 LTS+VMware8.0.4+gcc4.4.3;
二、開發工具:Eclipse+make
六:題記
一、上面的代碼不免有bug,若是你發現代碼寫的有問題,請你幫忙指出,讓咱們一塊兒進步,讓代碼變的更漂亮和更健壯;
二、我本身能手動寫上面代碼,離不開郝斌、高一凡、侯捷、嚴蔚敏等老師的書籍和視頻指導,在這裏感謝他們;
三、鼓勵本身能堅持把更多數據結構方面的知識寫出來,讓本身掌握更深入,也順便冒充下"小牛";
四、全部動畫都在網上找的,感謝製做作動畫的朋友,這樣好的動畫比圖片更便於你們理解複雜的內容;
歡迎繼續閱讀「啓迪思惟:數據結構和算法」系列