平衡二叉樹(AVL樹)的基本操作(附有示意圖)
阿新 • • 發佈:2018-12-25
平衡二叉樹關於樹的深度是平衡的,具有較高的檢索效率。平衡二叉樹或是一棵空樹,或是具有下列性質的二叉排序樹:其左子樹和右子樹都是平衡二叉樹,而且左右子樹深度之差絕對值不超過1. 由此引出了平衡因子(balance factor)的概念,bf定義為該結點的左子樹的深度減去右子樹的深度(有些書是右子樹深度減去左子樹深度,我是按照左子樹減去右子樹來計算的,下面的程式碼也是這樣定義的),所以平衡二叉樹的結點的平衡因子只可能是 -1,0,1 ,某個結點的平衡因子絕對值大於1,該二叉樹就不平衡。
平衡二叉樹在出現不平衡狀態的時候,要進行平衡旋轉處理,有四種平衡旋轉處理(單向右旋處理,單向左旋處理,雙向旋轉(先左後右)處理,雙向旋轉(先右後左)處理),歸根到底是兩種(單向左旋處理和單向右旋處理)。
檔案"tree.h"
#include<iostream> #include<stack> #include<queue> using namespace std; const int LH=1; //左子樹比右子樹高1 const int EH=0; //左右子樹一樣高 const int RH=-1;//右子樹比左子樹高1 const int MAX_NODE_NUM=20; //結點數目上限 class AVL_Tree; class AvlNode { int data; int bf; //平衡因子 AvlNode *lchild; AvlNode *rchild; friend class AVL_Tree; }; class AVL_Tree { public: int Get_data(AvlNode *p) { return p->data; } void Create_AVl(AvlNode *&T) //建樹 { cout<<"輸入平衡二叉樹的元素,輸入-1代表結束輸入:"; int num[MAX_NODE_NUM]; int a,i=0; while(cin>>a && a!=-1) { num[i]=a; i++; } if(num[0]==-1) { cout<<"平衡樹為空"<<endl; T=NULL; return; } int k=i; bool taller=false; for(i=0;i<k;i++) Insert_Avl(T,num[i],taller);//逐個進行插入,插入過程看下面的示意圖 cout<<"_____建樹完成____"<<endl; } void L_Rotate(AvlNode *&p) { //以p為根節點的二叉排序樹進行單向左旋處理 AvlNode *rc=p->rchild; p->rchild=rc->lchild; rc->lchild=p; p=rc; } void R_Rotate(AvlNode *&p) { //以p為根節點的二叉排序樹進行單向右旋處理 AvlNode *lc=p->lchild; p->lchild=lc->rchild; lc->rchild=p; p=lc; } void Left_Balance(AvlNode *&T) { //以T為根節點的二叉排序樹進行左平衡旋轉處理 AvlNode *lc,*rd; lc=T->lchild; switch(lc->bf) { case LH: //新結點插在T的左孩子的左子樹上,做單向右旋處理 T->bf=lc->bf=EH; R_Rotate(T); break; case RH: //新結點插在T的左孩子的右子樹上,要進行雙旋平衡處理(先左後右) rd=lc->rchild; switch(rd->bf) { case LH: //插在右子樹的左孩子上 T->bf=RH; lc->bf=EH; break; case EH: T->bf=lc->bf=EH; break; case RH: T->bf=EH; lc->bf=LH; break; } rd->bf=EH; L_Rotate(T->lchild);//先對T的左子樹進行單向左旋處理 R_Rotate(T); //再對T進行單向右旋處理 } } void Right_Balance(AvlNode *&T) { //以T為根節點的二叉排序樹進行右平衡旋轉處理 AvlNode *rc,*ld; rc=T->rchild; switch(rc->bf) { case RH: //新結點插在右孩子的右子樹上,進行單向左旋處理 T->bf=rc->bf=EH; L_Rotate(T); break; case LH: //新結點插在T的右孩子的左子樹上,要進行右平衡旋轉處理(先右再左) ld=rc->lchild; switch(ld->bf) { case LH: T->bf=LH; rc->bf=EH; break; case EH: T->bf=rc->bf=EH; break; case RH: T->bf=EH; rc->bf=RH; break; } ld->bf=EH; R_Rotate(T->rchild);//先對T的右子樹進行單向右旋處理 L_Rotate(T); //再對T進行單向左旋處理 } } bool Insert_Avl(AvlNode *&T,int num,bool &taller) //插入 { //若在平衡二叉樹中不存在結點值和num一樣大小的結點 //則插入值為num的新結點,並返回true //若因為插入而使得二叉排序樹失去平衡,則做平衡旋轉處理 //taller反映樹是否長高 if(!T) { //插入新結點,樹長高,taller為true T=new AvlNode; T->data=num; T->lchild=T->rchild=NULL; T->bf=EH; taller=true; } else { if(num==T->data) { //不重複插入 taller=false; return false; } if(num<T->data) //繼續在T的左子樹中進行搜尋 { if(!Insert_Avl(T->lchild,num,taller))//插入不成功 return false; if(taller) //已插入T的左子樹,且左子樹長高 { switch(T->bf) { case LH: /*————————————————————— / 插入前左子樹高於右子樹,需要進行做平衡處理 / 不管是單向左旋處理,還是先左後右平衡處理 / 處理結果都是使得插入新結點後,樹的高度不變 /—————————————————————*/ Left_Balance(T); taller=false; break; case EH: //插入前左右子樹等高,現在插入新街點後,左子樹比右子樹高 T->bf=LH; taller=true; break; case RH: //插入前右子樹比左子樹高,現在新結點插入左子樹後,樹變為左右子樹等高 T->bf=EH; taller=false; break; } } } else { //num>T->data 在T的右子樹中繼續搜尋 if(!Insert_Avl(T->rchild,num,taller)) return false; if(taller) { switch(T->bf) { case LH: //插入前左子樹比右子樹高,現在插入T的右子樹後,左右子樹等高 T->bf=EH; taller=false; break; case EH: //插入前左右子樹等高,現在插入後,右子樹比左子樹高 T->bf=RH; taller=true; break; case RH: //插入前右子樹比坐子樹高,插入後,排序樹失去平衡,需要進行右平衡處理 Right_Balance(T); taller=false; break; } } } } return true; } bool Search_Avl(AvlNode *T,int num,AvlNode *&f,AvlNode *&p) //搜尋 { //用p帶回查詢到的頂點的地址,f帶回p的雙親結點 p=T; while(p) { if(p->data==num) return true; if(p->data>num) { f=p; p=p->lchild; } else { f=p; p=p->rchild; } } return false; }
測試檔案"main.cpp"void Delete_AVL(AvlNode *&T,int num) //刪除,刪除後沒有回溯到根節點,演算法有錯,待日後修改完善,有心的朋友可以自己加一個棧或者其他方式來實現 { /*--------------------------------------------------------- / 從樹中刪除一個節點後,要保證刪後的樹還是一棵平衡二叉樹, / 刪除前,首先是在樹中查詢是否有這個結點,用p指向該結點, / 用f指向p的雙親結點,這個結點在樹中的位置有下面四種情況: / / 1:如果p指向的結點是葉子結點,那麼直接將f指標的左子樹或者 / 右子樹置空,然後刪除p結點即可。 / / 2:如果p指向的結點是隻有左子樹或右子樹,那麼只需要讓p結點 / 原來在f中的位置(左子樹或右子樹)用p的子樹代替即可。 / 代替後,要修改f的平衡因子,在失去平衡的時候,要呼叫相應的 / 做平衡旋轉或右平衡旋轉進行恢復. / / 3:如果p所指向的結點是根節點,那麼直接將根節點置空 / / 4:如果p所指向的結點左右子樹都非空,為了刪除p後原序列的順 / 序不變,就需要在原序列中先找出p的直接前驅(或者直接後繼) / 結點用那個結點的值來代替p結點的值,然後再刪掉那個直接前 / 驅(或者直接後繼)結點。 / 其中s指向的是要刪除的結點,也就是p的直接前驅,q指向的是 / s的雙親結點,此時,應該看s的平衡因子,在會出現失去平衡的 / 情況時,就要根據實際情況採用左平衡旋轉或是右平衡旋轉,讓 / 樹恢復平衡,這點和插入操作時是相對應的。 / / 在中序遍歷序列中找結點的直接前驅的方法是順著結點的左孩子 / 的右鏈域開始,一直到結點右孩子為空為止。 /---------------------------------------------------------*/ AvlNode *f=NULL; AvlNode *p=NULL; AvlNode *q=NULL; AvlNode *s=NULL; if(Search_Avl(T,num,f,p)) { if(p->lchild && p->rchild) //左右子樹均存在時 { q=p; s=p->lchild; while(s->rchild) { q=s; s=s->rchild; } p->data=s->data; if(q!=p) { //q結點的右子樹高度減少1 //所以平衡因子會+1 q->rchild=s->lchild; switch(q->bf) { //刪除前右子樹高,現在就變成一樣高 case RH: q->bf=EH; break; //刪除前等高,現在就變成左子樹比右子樹高 case EH: q->bf=LH; break; //刪除前左子樹高,現在左子樹又高了一,所以失去平衡 case LH: q->bf=EH; Left_Balance(q); break; } } else { //p的左子樹的右子樹為空時 //q結點也就是p結點,由於s的右子樹為空 //所以q結點的左子樹高度降低1 //平衡因子-1 q->lchild=s->lchild; switch(q->bf) { case LH: q->bf=EH;break; case EH: q->bf=RH;break; case RH: q->bf=EH; Right_Balance(q); break; } } delete s; cout<<"刪除結點成功"<<endl; return ; } else { if(!p->lchild) { q=p; p=p->rchild; } else { q=p; p=p->lchild; } if(!T) { T->bf=EH; T=p; } else if(q==f->lchild) { f->lchild=p; switch(f->bf) { case LH: f->bf=EH; break; case EH: f->bf=RH; break; case RH: f->bf=EH; Right_Balance(f); break; } } else { f->rchild=p; switch(f->bf) { case RH: f->bf=EH; break; case EH: f->bf=LH; break; case LH: f->bf=EH; Left_Balance(f); break; } } delete q; cout<<"刪除結點成功"<<endl; return; } } else { cout<<"要刪除的結點不存在"<<endl; return; } } InOrder_Traverse(AvlNode *T) //中序遍歷 { stack<AvlNode *> s; AvlNode *p=T; while(p || !s.empty()) { if(p) { s.push(p); p=p->lchild; } else { p=s.top(); s.pop(); cout<<p->data<<" "; p=p->rchild; } } } void Level_Traverse(AvlNode *T) //層次遍歷 { queue<AvlNode *> q; AvlNode *p=T; q.push(p); while(!q.empty()) { p=q.front(); q.pop(); cout<<p->data<<" "; if(p->lchild) q.push(p->lchild); if(p->rchild) q.push(p->rchild); } } };
#include"tree.h"
int main()
{
AVL_Tree tree;
AvlNode *root=NULL;
cout<<"____建立平衡二叉樹____"<<endl;
tree.Create_AVl(root);
cout<<"中序遍歷二叉樹為:";
tree.InOrder_Traverse(root);
cout<<endl;
cout<<"層次遍歷二叉樹為:";
tree.Level_Traverse(root);
cout<<endl;
int num;
bool taller=false;
cout<<"輸入你要插入的結點的值:";
cin>>num;
tree.Insert_Avl(root,num,taller);
cout<<"中序遍歷二叉樹為:";
tree.InOrder_Traverse(root);
cout<<endl;
AvlNode *f=NULL;
AvlNode *p=NULL;
cout<<"輸入你要搜尋的結點的值:";
cin>>num;
if(tree.Search_Avl(root,num,f,p))
{
cout<<"查詢得到的結點值為:"<<tree.Get_data(p)<<"的地址為:"<<p<<endl;
if(f==NULL)
cout<<"因為結點"<<tree.Get_data(p)<<"是根結點,所以沒有雙親結點"<<endl;
else
cout<<"該結點的雙親結點的值為:"<<tree.Get_data(f)<<endl;
}
else
cout<<"查詢的結點不存在"<<endl;
cout<<"輸入你要刪除的結點的值:";
cin>>num;
tree.Delete_AVL(root,num);
cout<<"中序遍歷二叉樹為:";
tree.InOrder_Traverse(root);
cout<<endl;
return 0;
}
測試結果
____建立平衡二叉樹____
輸入平衡二叉樹的元素,輸入-1代表結束輸入:16 3 7 11 9 26 18 14 15 -1
_____建樹完成____
中序遍歷二叉樹為:3 7 9 11 14 15 16 18 26
層次遍歷二叉樹為:11 7 18 3 9 15 26 14 16
輸入你要插入的結點的值:20
中序遍歷二叉樹為:3 7 9 11 14 15 16 18 20 26
輸入你要搜尋的結點的值:20
查詢得到的結點值為:20的地址為:00380BB0
該結點的雙親結點的值為:26
輸入你要刪除的結點的值:15
刪除結點成功
中序遍歷二叉樹為:3 7 9 11 14 16 18 20 26
Press any key to continue
下面是四種旋轉的示意圖
先右後左的和先左後右的相對應的 所以不畫了 下面再畫一個建樹的過程 就不畫了 太費時間了畫這個 剛好找到一個畫好的 直接貼
因為下面圖中它的計算平衡因子的方式是右子樹高度減去左子樹高度,所以和我上面的計算方法相反,不過不影響觀看
建立含有元素 16 3 7 11 9 26 18 14 15 的平衡二叉樹的過程如下所示: