查詢——平衡二叉樹的實現(程式碼超詳細註釋)
既然你搜索到了這篇文章,那麼平衡二叉樹的作用想必心中已經清楚了,我們接下來就直接來談談程式碼...
目錄
知識準備
啥?你又不知道,真拿你沒辦法,給你一篇講的不錯的文章:
進階講解
喂,看完別走呀,我再講點進階的知識,我們知道,平衡二叉樹的實現過程中最頭疼的就是實現旋轉操作,然而,我們可以換一種思維,我們不再去注意旋轉這個過程,而直接看重旋轉後的結果,旋轉後的結果無非是下面這種形式:
即三個節點和四棵子樹這種形式,而這裡的三個節點分別是失衡節點及其子節點和孫節點(失衡方向上的),四棵子樹則為這三個節點的孩子,無論對於左旋操作,右旋操作,左旋-右旋操作,右旋-左旋操作,它們旋轉後的結果都可以轉化成這種形式,不用考慮旋轉的過程大大節省了我們的腦細胞,而這種重構的方式的名字也名如其人——3+4重構
在平衡二叉樹中,我們還需要注意,對於節點刪除操作,會出現失衡傳播現象,即區域性失衡你把它重平衡後,它的祖先節點可能又失衡了,所以要我們要不斷向上檢查是否還有失衡的節點。
程式碼實現
有了以上的準備,我們就可以上史上最詳細註釋的程式碼了:
//BinTree.h #pragma once //二叉樹模板 //SJ2050 #include <stack> //二叉樹節點的模板定義 template <class ElemType> struct BinTreeNode { ElemType data; //節點的資料 BinTreeNode<ElemType> *parent; //父節點指標 BinTreeNode<ElemType> *leftChild; //左孩子指標 BinTreeNode<ElemType> *rightChild; //右孩子指標 unsigned int height; //樹高 //int bf; //平衡因子,這裡為左子樹的高度減去右子樹的高度(廢棄) }; //二叉樹的模板定義 template <class ElemType> class BinTree { public: BinTree(); //建構函式,進行初始化的操作 ~BinTree(); //解構函式,進行銷燬工作 virtual void Print()=0; //將二叉樹中的內容打印出來 virtual BinTreeNode<ElemType>* Search(ElemType data)=0; //搜尋節點函式 virtual bool Insert(ElemType data)=0; //插入節點函式 virtual bool Delete(ElemType data) = 0; //刪除節點函式 virtual void UpdateHeight(BinTreeNode<ElemType> &node); //更新樹高 protected: BinTreeNode<ElemType> *root; //根節點指標 }; //二叉樹建構函式的定義 template <class ElemType> BinTree<ElemType>::BinTree() { this->root = nullptr; //將根節點初始化為空 } //二叉樹解構函式的定義 template <class ElemType> BinTree<ElemType>::~BinTree() { stack<BinTreeNode<ElemType>*> stack; //使用STL中的棧 BinTreeNode<ElemType> *p = this->root; //p指標先指向二叉樹的根節點 if (p != nullptr) { stack.push(p); //將根節點壓入棧 while (!stack.empty()) //直至棧中無元素為止 { p = stack.top(); //p指向棧頂元素 stack.pop(); //棧彈出一個元素,即少一個元素 if (p->leftChild != nullptr) { stack.push(p->leftChild); //將左孩子節點壓入棧 } if (p->rightChild != nullptr) { stack.push(p->rightChild); //將右孩子節點壓入棧 } delete p; //刪除p指向的節點 } } this->root = nullptr; //將樹的根節點置空 } //函式功能:更新樹中節點的樹高 //函式引數:開始更新的節點的引用 //函式返回值:void template <class ElemType> void BinTree<ElemType>::UpdateHeight(BinTreeNode<ElemType> &node) { //該函式自底向上更新各節點的樹高 BinTreeNode<ElemType> *p = &node; while (p != nullptr) { if (p->leftChild == nullptr) { //當該節點無左孩子時 p->height = p->rightChild == nullptr ? 1 : p->rightChild->height + 1; } else if (p->rightChild == nullptr) { //當該節點無右孩子時 p->height = p->leftChild == nullptr ? 1 : p->leftChild->height + 1; } else { //當該節點有左右孩子時 p->height = p->leftChild->height > p->rightChild->height ? \ p->leftChild->height + 1 : p->rightChild->height + 1; } p = p->parent; //自底而上更新樹高 } }
以上為二叉樹類的模板
//AVL.h #pragma once //平衡二叉樹模板 //SJ2050 #include "BinTree.h" #define OK 1 #define FALSE 0 //平衡二叉樹類模板定義 template <class ElemType> class AVL : public BinTree<ElemType> { public: void Print(); //將二叉樹中的內容打印出來 BinTreeNode<ElemType>* Search(ElemType data); //搜尋節點函式 bool Insert(ElemType data); //插入節點函式 bool Delete(ElemType data); //刪除節點函式 private: void Rotate(BinTreeNode<ElemType> &unbalancedNode); //旋轉函式 void Connect34(BinTreeNode<ElemType> *a, BinTreeNode<ElemType> *b, BinTreeNode<ElemType> *c,\ BinTreeNode<ElemType> *T0, BinTreeNode<ElemType> *T1, BinTreeNode<ElemType> *T2,\ BinTreeNode<ElemType> *T3); //3+4重構函式 int CalculateBF(BinTreeNode<ElemType> &node); //計算失衡值 void PrintOut(BinTreeNode<ElemType> *beginNode); //採用中序遍歷對二叉樹進行列印 }; //函式功能:搜尋待查詢的節點,並返回其位置(供使用者呼叫) //函式引數:待查詢的值 //函式返回值:待查詢的節點的指標或該節點應該出現位置的父節點的指標,若樹為空返回nullptr template <class ElemType> BinTreeNode<ElemType>* AVL<ElemType>::Search(ElemType data) //搜尋節點函式 { if (this->root == nullptr) { //當樹還為空時 return nullptr; } BinTreeNode<ElemType> *x = this->root; //x節點 BinTreeNode<ElemType> *p = x->parent; //p為x的父節點 while (x != nullptr && x->data != data ) { p = x; if (data > x->data) { x = x->rightChild; } else { x = x->leftChild; } } if (x == nullptr) { //當搜尋不到要查詢的節點時,返回應出現位置的父節點的指標 return p; } else { //當搜尋到要查詢的節點時,返回該節點的指標 return x; } } //函式功能:3+4重構實現旋轉操作(Private) //函式引數:三個節點和四棵子樹的二級指標 //函式返回值:void template <class ElemType> void AVL<ElemType>::Connect34(BinTreeNode<ElemType> *a, BinTreeNode<ElemType> *b, \ BinTreeNode<ElemType> *c, \ BinTreeNode<ElemType> *T0, BinTreeNode<ElemType> *T1, \ BinTreeNode<ElemType> *T2, BinTreeNode<ElemType> *T3) { //a,c為b的左右孩子,T0,T1,T2,T3又分別為a,c的左右子樹 b->leftChild = a; a->parent = b; b->rightChild = c; c->parent = b; a->leftChild = T0; if (T0) T0->parent = a; //T0可能為空 a->rightChild = T1; if (T1) T1->parent = a; //T1可能為空 c->leftChild = T2; if (T2) T2->parent = c; //T2可能為空 c->rightChild = T3; if (T3) T3->parent = c; //T3可能為空 //更新三個節點的樹高 UpdateHeight(*a); UpdateHeight(*c); UpdateHeight(*b); } //函式功能:計算節點的失衡值(Private) //函式引數:要計算的節點的引用 //函式返回值:計算得到的失衡值 template <class ElemType> int AVL<ElemType>::CalculateBF(BinTreeNode<ElemType> &node) { //失衡值計算方法為左子樹高減去右子樹高 int leftTreeHeight = (node.leftChild == nullptr ? 0 : node.leftChild->height); //左子樹的高 int rightTreeHeight = (node.rightChild == nullptr ? 0 : node.rightChild->height); //右子樹的高 return leftTreeHeight - rightTreeHeight; } //函式功能:進行旋轉操作(Private) //函式引數:失衡節點引用 //函式返回值:void template <class ElemType> void AVL<ElemType>::Rotate(BinTreeNode<ElemType> &unbalancedNode) { int bf = CalculateBF(unbalancedNode); //計算出失衡節點的平衡值 if (bf > 0) { if (CalculateBF(*unbalancedNode.leftChild) >= 0) { //zig型 BinTreeNode<ElemType> *x = unbalancedNode.leftChild->leftChild; //失衡節點的孫節點 BinTreeNode<ElemType> *p = unbalancedNode.leftChild; //失衡節點的子節點 BinTreeNode<ElemType> *g = &unbalancedNode; //失衡節點 //p頂替g的位置 p->parent = g->parent; if (g->parent != nullptr) { //失衡節點有父節點時 if (g->parent->leftChild == g) g->parent->leftChild = p; else g->parent->rightChild = p; } else { //失衡節點無父節點時 this->root = p; } Connect34(x, p, g, x->leftChild, x->rightChild, p->rightChild, g->rightChild); } else { //zag-zig型 BinTreeNode<ElemType> *x = unbalancedNode.leftChild->rightChild; //失衡節點的孫節點 BinTreeNode<ElemType> *p = unbalancedNode.leftChild; //失衡節點的子節點 BinTreeNode<ElemType> *g = &unbalancedNode; //失衡節點 //x頂替g的位置 x->parent = g->parent; if (g->parent != nullptr) { //失衡節點有父節點時 if (g->parent->leftChild == g) g->parent->leftChild = x; else g->parent->rightChild = x; } else { //失衡節點無父節點時 this->root = x; } Connect34(p, x, g, p->leftChild, x->leftChild, x->rightChild, g->rightChild); } } else if (bf < 0) { if (CalculateBF(*unbalancedNode.rightChild) <= 0) { //zag型 BinTreeNode<ElemType> *x = unbalancedNode.rightChild->rightChild; //失衡節點的孫節點 BinTreeNode<ElemType> *p = unbalancedNode.rightChild; //失衡節點的子節點 BinTreeNode<ElemType> *g = &unbalancedNode; //失衡節點 //p頂替g的位置 p->parent = g->parent; if (g->parent != nullptr) { //失衡節點有父節點時 if (g->parent->leftChild == g) g->parent->leftChild = p; else g->parent->rightChild = p; } else { //失衡節點無父節點時 this->root = p; } Connect34(g, p, x, g->leftChild, p->leftChild, x->leftChild, x->rightChild); } else { //zig-zag型 BinTreeNode<ElemType> *x = unbalancedNode.rightChild->leftChild; //失衡節點的孫節點 BinTreeNode<ElemType> *p = unbalancedNode.rightChild; //失衡節點的子節點 BinTreeNode<ElemType> *g = &unbalancedNode; //失衡節點 //x頂替g的位置 x->parent = g->parent; if (g->parent != nullptr) { //失衡節點有父節點時 if (g->parent->leftChild == g) g->parent->leftChild = x; else g->parent->rightChild = x; } else { //失衡節點無父節點時 this->root = x; } Connect34(g, x, p, g->leftChild, x->leftChild, x->rightChild, p->rightChild); } } } //函式功能:插入操作(供使用者呼叫) //函式引數:要插入的節點的資料 //函式返回值:bool型別,返回OK or FALSE template <class ElemType> bool AVL<ElemType>::Insert(ElemType data) { BinTreeNode<ElemType> *posi = Search(data); //記錄待插入節點的位置 if (posi != nullptr && posi->data == data) { //當要插入的資料已經存在時 return FALSE; } BinTreeNode<ElemType> *node; node = new BinTreeNode<ElemType>; //將待插入的資料包裝成節點 node->data = data; node->height = 1; node->leftChild = node->rightChild = nullptr; node->parent = posi; if (posi != nullptr) { //當樹不為空時 (data < posi->data ? posi->leftChild : posi->rightChild) = node; } else { //當樹還為空時 this->root = node; } UpdateHeight(*node); BinTreeNode<ElemType> *g; //g為插入節點的爺節點 g = node->parent; if (g != nullptr) { while (g != nullptr&&abs(CalculateBF(*g)) <= 1) { //向上查詢失衡節點 g = g->parent; } if (g != nullptr) { //當存在失衡節點時,進行旋轉操作 Rotate(*g); //若爺節點的失衡值的絕對值大於1,進行旋轉操作 } } return OK; } //函式功能:刪除節點操作(供使用者呼叫) //函式引數:待刪除的資料 //函式返回值:bool型別,OK or FALSE template <class ElemType> bool AVL<ElemType>::Delete(ElemType data) { BinTreeNode<ElemType> *posi = this->Search(data); //查詢待刪除的節點 if (posi == nullptr || posi->data != data) { //當找不到要刪除的節點時,返回FALSE return FALSE; } BinTreeNode<ElemType> *succ; //待刪除節點的接替節點,這裡用它的前驅結點 BinTreeNode<ElemType> *unbalancedCheckNode; //失衡檢查節點 if (posi->leftChild == nullptr) { //當刪除節點的左孩子為空時 succ = posi->rightChild; if (posi->parent == nullptr) { //當要刪除的節點即為根節點時 this->root = succ; //將樹的根節點替換成要刪除節點的接替節點 if (succ != nullptr) succ->parent = nullptr; //修改接替節點的父節點 } else { //當要刪除的節點不為根節點時 (posi->parent->leftChild == posi ? posi->parent->leftChild : posi->parent->rightChild) = succ; //posi的父節點的孩子替換為succ if (succ != nullptr) succ->parent = posi->parent; //修改接替節點的父節點 } unbalancedCheckNode = posi->parent; //向上檢查失衡 delete posi; //刪除節點 posi = nullptr; //將節點置空 } else if (posi->leftChild->rightChild == nullptr) { //當刪除節點的左孩子的右孩子為空時 succ = posi->leftChild; if (posi->parent == nullptr) { //當要刪除的節點即為根節點時 this->root = succ; //將樹的根節點替換成要刪除節點的接替節點 succ->parent = nullptr; //修改接替節點的父節點 succ->rightChild = posi->rightChild; //接替節點的右孩子變為刪除節點的右孩子 if (posi->rightChild != nullptr) posi->rightChild->parent = succ; } else { //當要刪除的節點不為根節點時 (posi->parent->leftChild == posi ? posi->parent->leftChild : posi->parent->rightChild) = succ; //posi的父節點的孩子替換為succ succ->parent = posi->parent; succ->rightChild = posi->rightChild; //接替節點的右孩子變為刪除節點的右孩子 if (posi->rightChild != nullptr) posi->rightChild->parent = succ; } unbalancedCheckNode = succ; //向上檢查失衡 delete posi; //刪除節點 posi = nullptr; //將節點置空 } else { //當刪除結點的左孩子不為空且左孩子的右孩子不為空 succ = posi->leftChild; while (succ->rightChild != nullptr) { //尋找刪除節點的前驅結點 succ = succ->rightChild; } posi->data = succ->data; //將刪除結點的資料用接替結點的資料代替 unbalancedCheckNode = succ->parent; succ->parent->rightChild = succ->leftChild; //接替節點的左孩子替代接替節點的父節點的右孩子 if (succ->leftChild != nullptr) succ->leftChild->parent = succ->parent; //更新接替節點左孩子的父節點 delete succ; //刪除結點 succ = nullptr; //將指標置空 } UpdateHeight(*unbalancedCheckNode); //更新樹高 BinTreeNode<ElemType> *ancestorNode = unbalancedCheckNode; //刪除節點的祖先結點 while (ancestorNode != nullptr) { //由於刪除操作可能回引起失衡傳播,所以要一直向上檢查是否失衡 if (abs(CalculateBF(*ancestorNode))>1) { //當失衡值的絕對值大於1時進行旋轉操作 Rotate(*ancestorNode); } ancestorNode = ancestorNode->parent; } return OK; } //函式功能:將平衡二叉樹的節點資料打印出來(供使用者呼叫) //函式引數:無 //函式返回值:void template <class ElemType> void AVL<ElemType>::Print() { PrintOut(this->root); } //函式功能:將平衡二叉樹的節點資料打印出來(Private) //函式引數:開始列印的節點的指標 //函式返回值:void template <class ElemType> void AVL<ElemType>::PrintOut(BinTreeNode<ElemType> *beginNode) { //採用中序遍歷 if (beginNode != nullptr) { this->PrintOut(beginNode->leftChild); std::cout << beginNode->data << "\t"; this->PrintOut(beginNode->rightChild); } }
以上為平衡二叉樹類的模板(繼承二叉樹類)
我想說的,基本上程式碼中都註釋了。為了程式的安全性考慮,我也儘可能採用引用代替指標以及把一些類函式定義為私有函式,避免被使用者濫用。但有同學可能會吐槽這程式碼怎麼會這麼長,我只想說,詳細註釋是有代價的,當然,程式碼片段中存在部分重複,這是未優化的結果,如果同學們有什麼好的改進意見可以在評論區留言給我。上述對二叉樹類的定義也過分簡單,這是因為這個二叉樹類模板我是第一次創建出來的,以後會慢慢會豐富該類模板的介面,不過在這個程式中,這個簡單的不能再簡單的二叉樹類是完全足夠的。除此之外,我還是要吐槽這個程式碼中模板用的形如虛設,因為未對比較操作符(<>=)進行過載,這就導致了比較的範圍十分有限,如果再過載比較操作符的話,我們就能比較筆和橡皮的大小了,同學們可以一試。
接下來未測試主函式中的內容:
//平衡二叉樹測試函式
//SJ2050
#include <iostream>
#include "AVL.h"
using namespace std;
int main()
{
AVL<int> *T = new AVL<int>; //建立一棵平衡二叉樹
int a[10] = { 3,2,1,4,5,6,7,10,9,8 };
for (int i = 0; i < 10; i++)
{
T->Insert(a[i]);
}
T->Print();
cout << endl; //換行
T->Delete(4);
T->Print();
cout << endl; //換行
T->Delete(2);
T->Print();
cout << endl; //換行
T->Delete(7);
T->Print();
cout << endl; //換行
T->Delete(8);
T->Print();
cout << endl; //換行
T->Insert(11);
T->Print();
cout << endl; //換行
delete T;
system("pause");
return 0;
}
輸出結果為:
雖然該程式經過我多次除錯,但還是可能存在bug,如果你發現的話,請在評論區告訴我,我會馬上修改,謝謝閱讀!