二叉排序樹(Binary Sort Tree)

一、定義 

    二叉排序樹(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是二叉排序樹的類型

四、二叉排序樹運算

(1)二叉排序樹的插入和生成

    ①  二叉排序樹插入新結點的過程

    在二叉排序樹中插入新結點,要保證插入後仍知足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倍。所以在通常狀況下,構造二叉排序樹的目的並不是爲了排序,而是用它來加速查找,這是由於在一個有序的集合上查找一般比在無序集合上查找更快。所以,人們又經常將二叉排序樹稱爲二叉查找樹。

(2)二叉排序樹的刪除

    從二叉排序樹中刪除一個結點,不能把以該結點爲根的子樹都刪去,而且還要保證刪除後所得的二叉樹仍然知足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

(3) 二叉排序樹上的查找

    ①  查找遞歸算法

    在二叉排序樹上進行查找,和二分查找相似,也是一個逐步縮小查找範圍的過程。

    遞歸的查找算法:

/*在二叉排序樹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樹是接近最優的。

相關文章
相關標籤/搜索