二叉排序樹(Binary Sort Tree)又稱二叉查找(搜索)樹(Binary Search Tree)。其定義爲:二叉排序樹或者是空樹,或者是知足以下性質的二叉樹:node
① 若它的左子樹非空,則左子樹上全部結點的值均小於根結點的值;算法
② 若它的右子樹非空,則右子樹上全部結點的值均大於根結點的值;性能
③ 左、右子樹自己又各是一棵二叉排序樹。spa
上述性質簡稱二叉排序樹性質(BST性質),故二叉排序樹其實是知足BST性質的二叉樹。指針
注意:code
當用線性表做爲表的組織形式時,能夠有三種查找法。其中以二分查找效率最高。但因爲二分查找要求表中結點按關鍵字有序,且不能用鏈表做存儲結構,所以,當表的插入或刪除操做頻繁時,爲維護表的有序性,勢必要移動表中不少結點。這種由移動結點引發的額外時間開銷,就會抵消二分查找的優勢。也就是說,二分查找只適用於靜態查找表。若要對動態查找表進行高效率的查找,可採用下二叉樹或樹做爲表的組織形式。不妨將它們統稱爲樹表。排序
由BST性質可得:遞歸
(1) 二叉排序樹中任一結點x,其左(右)子樹中任一結點y(若存在)的關鍵字必小(大)於x的關鍵字。class
(2) 二叉排序樹中,各結點關鍵字是唯一的。效率
注意:
實際應用中,不能保證被查找的數據集中各元素的關鍵字互不相同,因此可將二叉排序樹定義中BST性質(1)裏的"小於"改成"大於等於",或將BST性質(2)裏的"大於"改成"小於等於",甚至可同時修改這兩個性質。
(3) 按中序遍歷該樹所獲得的中序序列是一個遞增有序序列。
【例】下圖所示的兩棵樹均是二叉排序樹,它們的中序序列均爲有序序列:2,3,4,5,7,8。
二叉樹結構體定義;
typedef struct BiTNode { int data; //結點數據 struct BiTNode *lChild,*rChild; }BiTNode,*BiTree;
或以下定義:
typedef int KeyType; //假定關鍵字類型爲整數 typedef struct node { KeyType key; //關鍵字項 InfoType otherinfo; //其它數據域,InfoType視應用狀況而定,下面不處理它 struct node *lchild,*rchild; //左右孩子指針 } BSTNode; typedef BSTNode *BSTree; //BSTree是二叉排序樹的類型
① 二叉排序樹插入新結點的過程
在二叉排序樹中插入新結點,要保證插入後仍知足BST性質。其插入過程是:
a、若二叉排序樹T爲空,則爲待插入的關鍵字key申請一個新結點,並令其爲根;
b、若二叉排序樹T不爲空,則將key和根的關鍵字比較:
(i)若兩者相等,則說明樹中已有此關鍵字key,無須插入。
(ii)若key<T→key,則將key插入根的左子樹中。
(iii)若key>T→key,則將它插入根的右子樹中。
子樹中的插入過程與上述的樹中插入過程相同。如此進行下去,直到將key做爲一個新的葉結點的關鍵字插入到二叉排序樹中,或者直到發現樹中已有此關鍵字爲止。
② 二叉排序樹插入新結點的遞歸算法
③ 二叉排序樹插入新結點的非遞歸算法
void InsertBST(BSTree *Tptr,KeyType key) { //若二叉排序樹 *Tptr中沒有關鍵字爲key,則插入,不然直接返回 BSTNode *f,*p=*TPtr; //p的初值指向根結點 while(p){ //查找插入位置 if(p->key==key) return;//樹中已有key,無須插入 f=p; //f保存當前查找的結點 p=(key<p->key)?p->lchild:p->rchild; //若key<p->key,則在左子樹中查找,不然在右子樹中查找 } //endwhile p=(BSTNode *)malloc(sizeof(BSTNode)); p->key=key; p->lchild=p->rchild=NULL; //生成新結點 if(*TPtr==NULL) //原樹爲空 *Tptr=p; //新插入的結點爲新的根 else //原樹非空時將新結點關p做爲關f的左孩子或右孩子插入 if(key<f->key) f->lchild=p; else f->rchild=p; } //InsertBST
④ 二叉排序樹的生成
二叉排序樹的生成,是從空的二叉排序樹開始,每輸入一個結點數據,就調用一次插入算法將它插入到當前已生成的二叉排序樹中。生成二叉排序樹的算法以下:
BSTree CreateBST(void) { //輸入一個結點序列,創建一棵二叉排序樹,將根結點指針返回 BSTree T=NULL; //初始時T爲空樹 KeyType key; scanf("%d",&key); //讀人一個關鍵字 while(key){ //假設key=0是輸人結束標誌 InsertBST(&T,key); //將key插入二叉排序樹T scanf("%d",&key);//讀人下一關鍵字 } return T; //返回創建的二叉排序樹的根指針 } //BSTree
注意:
輸入序列決定了二叉排序樹的形態。
二叉排序樹的中序序列是一個有序序列。因此對於一個任意的關鍵字序列構造一棵二叉排序樹,其實質是對此關鍵字序列進行排序,使其變爲有序序列。"排序樹"的名稱也由此而來。一般將這種排序稱爲樹排序(Tree Sort),能夠證實這種排序的平均執行時間亦爲O(nlgn)。
對相同的輸入實例,樹排序的執行時間約爲堆排序的2至3倍。所以在通常狀況下,構造二叉排序樹的目的並不是爲了排序,而是用它來加速查找,這是由於在一個有序的集合上查找一般比在無序集合上查找更快。所以,人們又經常將二叉排序樹稱爲二叉查找樹。
從二叉排序樹中刪除一個結點,不能把以該結點爲根的子樹都刪去,而且還要保證刪除後所得的二叉樹仍然知足BST性質。
① 刪除操做的通常步驟
a、進行查找
查找時,令p指向當前訪問到的結點,parent指向其雙親(其初值爲NULL)。若樹中找不到被刪結點則返回,不然被刪結點是*p。
b、刪去*p。
刪*p時,應將*p的子樹(如有)仍鏈接在樹上且保持BST性質不變。按*p的孩子數目分三種狀況進行處理。
② 刪除*p結點的三種狀況
a、*p是葉子(即它的孩子數爲0)
無須鏈接*p的子樹,只需將*p的雙親*parent中指向*p的指針域置空便可。
b、*p只有一個孩子*child
只需將*child和*p的雙親直接鏈接後,便可刪去*p。
注意:
*p既多是*parent的左孩子也多是其右孩子,而*child多是*p的左孩子或右孩子,故共有4種狀態。
c、*p有兩個孩子
先令q=p,將被刪結點的地址保存在q中;而後找*q的中序後繼*p,並在查找過程當中仍用parent記住*p的雙親位置。*q的中序後繼*p必定是*q的右子樹中最左下的結點,它無左子樹。所以,能夠將刪去*q的操做轉換爲刪去的*p的操做,即在釋放結點*p以前將其數據複製到*q中,就至關於刪去了*q。。
③ 二叉排序樹刪除算法
分析:
上述三種狀況都能統一到狀況(2),算法中只需針對狀況(2)處理便可。
注意邊界條件:若parent爲空,被刪結點*p是根,故刪去*p後,應將child置爲根。
算法:
void DelBSTNode(BSTree *Tptr,KeyType key) {//在二叉排序樹*Tptr中刪去關鍵字爲key的結點 BSTNode *parent=NUll,*p=*Tptr,*q,*child; while(p){ //從根開始查找關鍵字爲key的待刪結點 if(p->key==key) break;//已找到,跳出查找循環 parent=p; //parent指向*p的雙親 p=(key<p->key)?p->lchild:p->rchild; //在關p的左或右子樹中繼續找 } if(!p) return; //找不到被刪結點則返回 q=p; //q記住被刪結點*p if(q->lchild&&q->rchild) //*q的兩個孩子均非空,故找*q的中序後繼*p for(parent=q,p=q->rchild; p->lchild; parent=p,p=p=->lchild); //如今狀況(3)已被轉換爲狀況(2),而狀況(1)至關因而狀況(2)中child=NULL的情況 child=(p->lchild)?p->lchild:p->rchild;//如果狀況(2),則child非空;不然child爲空 if(!parent) //*p的雙親爲空,說明*p爲根,刪*p後應修改根指針 *Tptr=child; //如果狀況(1),則刪去*p後,樹爲空;不然child變爲根 else{ //*p不是根,將*p的孩子和*p的雙親進行鏈接,*p從樹上被摘下 if(p==parent->lchild) //*p是雙親的左孩子 parent->lchild=child; //*child做爲*parent的左孩子 else parent->rchild=child; //*child做爲 parent的右孩子 if(p!=q) //是狀況(3),需將*p的數據複製到*q q->key=p->key; //若還有其它數據域亦需複製 } //endif free(p); /釋放*p佔用的空間 } //DelBSTNode
① 查找遞歸算法
在二叉排序樹上進行查找,和二分查找相似,也是一個逐步縮小查找範圍的過程。
遞歸的查找算法:
/*在二叉排序樹T上查找關鍵字爲key的結點,成功時返回該結點位置,不然返回NUll*/ BSTNode *SearchBST(BSTree T,KeyType key) { if(T==NULL||key==T->key) //遞歸的終結條件 return T; //T爲空,查找失敗;不然成功,返回找到的結點位置 if(key<T->key) return SearchBST(T->lchild,key); else return SearchBST(T->rchild,key); //繼續在右子樹中查找 }
② 算法分析
在二叉排序樹上進行查找時,若查找成功,則是從根結點出發走了一條從根到待查結點的路徑。若查找不成功,則是從根結點出發走了一條從根到某個葉子的路徑。
a、二叉排序樹查找成功的平均查找長度
在等機率假設下,下面(a)圖中二叉排序樹查找成功的平均查找長度爲
在等機率假設下,(b)圖所示的樹在查找成功時的平均查找長度爲:
ASLb=(1+2+3+4+5+6+7+8+9+10)/10=5.5
注意:
與二分查找相似,和關鍵字比較的次數不超過樹的深度。
b、在二叉排序樹上進行查找時的平均查找長度和二叉樹的形態有關
二分查找法查找長度爲n的有序表,其斷定樹是唯一的。含有n個結點的二叉排序樹卻不唯一。對於含有一樣一組結點的表,因爲結點插入的前後次序不一樣,所構成的二叉排序樹的形態和深度也可能不一樣
【例】下圖(a)所示的樹,是按以下插入次序構成的:
45,24,55,12,37,53,60,28,40,70
下圖(b)所示的樹,是按以下插入次序構成的:
12,24,28,37,40,45,53,55,60,70
在二叉排序樹上進行查找時的平均查找長度和二叉樹的形態有關:
① 在最壞狀況下,二叉排序樹是經過把一個有序表的n個結點依次插入而生成的,此時所得的二叉排序樹蛻化爲棵深度爲n的單支樹,它的平均查找長度和單鏈表上的順序查找相同,亦是(n+1)/2。
② 在最好狀況下,二叉排序樹在生成的過程當中,樹的形態比較勻稱,最終獲得的是一棵形態與二分查找的斷定樹類似的二叉排序樹,此時它的平均查找長度大約是lgn。
③ 插入、刪除和查找算法的時間複雜度均爲O(lgn)。
就平均時間性能而言,二叉排序樹上的查找和二分查找差很少。
就維護表的有序性而言,二叉排序樹無須移動結點,只需修改指針便可完成插入和刪除操做,且其平均的執行時間均爲O(lgn),所以更有效。二分查找所涉及的有序表是一個向量,如有插入和刪除結點的操做,則維護表的有序性所花的代價是O(n)。當有序表是靜態查找表時,宜用向量做爲其存儲結構,而採用二分查找實現其查找操做;如有序表裏動態查找表,則應選擇二叉排序樹做爲其存儲結構。
爲了保證二叉排序樹的高度爲lgn,從而保證然二叉排序樹上實現的插入、刪除和查找等基本操做的平均時間爲O(lgn),在往樹中插入或刪除結點時,要調整樹的形態來保持樹的"平衡。使之既保持BST性質不變又保證樹的高度在任何狀況下均爲O(lgn),從而確保樹上的基本操做在最壞狀況下的時間均爲O(lgn)。
注意:
①平衡二叉樹(Balanced Binary Tree)是指樹中任一結點的左右子樹的高度大體相同。
②任一結點的左右子樹的高度均相同(如滿二叉樹),則二叉樹是徹底平衡的。一般,只要二叉樹的高度爲O(1gn),就可看做是平衡的。
③平衡的二叉排序樹指知足BST性質的平衡二叉樹。
④AVL樹中任一結點的左、右子樹的高度之差的絕對值不超過1。在最壞狀況下,n個結點的AVL樹的高度約爲1.44lgn。而徹底平衡的二叉樹度高約爲lgn,AVL樹是接近最優的。