【筆記】二叉排序樹

Time-space發表於2017-11-19

  二叉排序樹也稱為二叉查詢樹。二叉排序樹的查詢是一種常用的動態查詢方法。

1.二叉排序樹的定義

  二叉排序樹或者是一棵空二叉樹,或者二叉樹具有下列性質:

  1. 如果二叉樹的左子樹不為空,則左子樹上的每一個結點的值均小於其對應根結點的值。
  2. 如果二叉樹的右子樹不為空,則右子樹上的每一個結點的值均大於其對應根結點的值。
  3. 該二叉樹的左子樹和右子樹也滿足性質1和性質2,即左子樹和右子樹也是一棵二叉排序樹。


這裡寫圖片描述

  • 型別定義
typedef int KeyType;
typedef struct      /*元素的定義*/
{
    KeyType key;
}DataType;
typedef struct Node /*二叉排序樹的型別定義*/
{
    DataType data;
    struct Node *lchild,*rchild;
}BiTreeNode,*BiTree;

2.二叉排序樹的查詢

  二叉排序樹中每個結點的值都大於其所有左子樹結點的值,而小於其所有右子樹中結點的值,如果要查詢與二叉樹中某個關鍵字相等的結點,可以從根結點開始,與給定的關鍵字比較,如果相等,則查詢成功;如果關鍵字小於根結點的值,則在其左子樹中查詢;如果給定的關鍵字大於根結點的值,則在其右子樹中查詢。


這裡寫圖片描述

  在二叉排序樹的查詢過程中,查詢某個結點的過程正好是走了從根結點到要查詢結點的路徑,其比較次數正好是路徑長度+1,這類似於折半查詢,與折半查詢不同的是由n個結點構成的判定樹是唯一的,而由n個結點構成的二叉排序樹則不唯一。


這裡寫圖片描述

BiTree BSTSearch(BiTree T,DataType x)
/*二叉排序樹的查詢,如果找到元素x,則返回指向結點的指標,否則返回NULL*/
{
    BiTreeNode *p;
    if(T!=NULL)                     /*如果二叉排序樹不為空*/
    {
        p=T;
        while(p!=NULL)
        {
            if(p->data.key==x.key)  /*如果找到,則返回指向該結點的指標*/
                return p;
            else if(x.key<p->data.key)  /*如果關鍵字小於p指向的結點的值,則在左子樹中查詢*/
                p=p->lchild;
            else
                p=p->rchild;    /*如果關鍵字大於p指向的結點的值,則在右子樹中查詢*/
        }
    }
    return NULL;
}
  • 二叉排序樹的插入

  二叉排序樹的結構不是一次生成的,而是在查詢的過程中,當樹中不存在關鍵字等於給定值結點時再進行插入。新插入的即誒但那一定是一個新新增的葉子結點,並且是查詢不成功時查詢路徑上訪問的最後一個結點的左孩子結點或右孩子結點。因此二叉排序樹的插入操作過程其實就是二叉排序樹的建立過程。
  演算法實現過程中,需要設定一個指向下一個要訪問結點的雙親結點指標parent,記下前驅結點的位置,以便在查詢失敗時進行插入操作。若在二叉樹中查詢x結束後返回NULL,說明查詢失敗,需要將x插入二叉排序樹;若parent>data.key<x.key

parent->data.key<x.key
,則需要將parent的左指標指向x,使x成為parent的左孩子結點;若parent>data.key>x.key
parent->data.key>x.key
,則需要將parent的右指標指向x,使x成為parent的右孩子結點;若二叉排序樹為空樹,則使x稱為根結點。


這裡寫圖片描述

  因此構造二叉排序樹的過程就是對一個無序的序列排序的過程,且每次插入結點都是葉子結點,在二叉排序樹中的插入操作過程中,不需要移動結點,僅需要移動結點指標,實現較為容易。

int BSTInsert(BiTree *T,DataType x)
/*二叉排序樹的插入操作,如果樹中不存在元素x,則將x插入到正確的位置並返回1,否則返回0*/
{
    BiTreeNode *p,*cur,*parent=NULL;
    cur=*T;
    while(cur!=NULL)
    {
        if(cur->data.key==x.key)    /*如果二叉樹中存在元素為x的結點,則返回0*/
            return 0;
        parent=cur;             /*parent指向cur的前驅結點*/
        if(x.key<cur->data.key)     /*如果關鍵字小於p指向的結點的值,則在左子樹中查詢*/
            cur=cur->lchild;
        else
            cur=cur->rchild;            /*如果關鍵字大於p指向的結點的值,則在右子樹中查詢*/
    }
    p=(BiTreeNode*)malloc(sizeof(BiTreeNode));  /*生成結點*/
    if(!p)
        exit(-1);
    p->data=x;
    p->lchild=NULL;
    p->rchild=NULL;
    if(!parent)                     /*如果二叉樹為空,則第一結點成為根結點*/
        *T=p;
    else if(x.key<parent->data.key)     /*如果關鍵字小於parent指向的結點,則將x成為parent的左孩子*/
        parent->lchild=p;
    else                            /*如果關鍵字大於parent指向的結點,則將x成為parent的右孩子*/
        parent->rchild=p;
    return 1;   
}

4.二叉排序樹的刪除

  對於二叉樹來說,刪除樹中的一個結點其實是刪除有序序列的一個記錄,只要在刪除某個結點之後依舊保持二叉樹的特性即可。
  假設要刪除的結點由指標s指示,指標p指向s的雙親結點,設s為f的左孩子結點。刪除一個結點分為如下3種情況討論:

  1. 如果s指向的結點為葉子結點,其左子樹和右子樹為空,刪除葉子不會影響到樹的結構特性,因此只需要修改p的指標即可。
  2. 如果s指向的結點只有左子樹或只有右子樹,在刪除了結點*s後,只需要將s的左子樹sL
    s_L
    或右子樹sR
    s_R
    作為f的左孩子,即p>lchild=s>child
    p->lchild=s->child
    p>lchild=s>rchild
    p->lchild=s->rchild
  3. 若s存在左子樹和右子樹,在刪除結點T之前,二叉排序樹的中序序列為{QLQXLXYLYTTRP}
    \{…Q_LQ…X_LXY_LYTT_RP…\}
    ,因此在刪除了結點S之後,使該二叉樹仍然保持原來的性質不變的調整方法有兩種:一種是使結點T的左子樹作為結點P的左子樹,結點T的右子樹作為結點Y的右子樹;另一種是使結點T的直接前驅取代結點T,並刪除T的直接前驅結點Y,然後令結點Y原來的左子樹作為結點X的右子樹。


這裡寫圖片描述

int BSTDelete(BiTree *T,DataType x)
/*在二叉排序樹T中存在值為x的資料元素時,刪除該資料元素結點,並返回1,否則返回0 */
{ 
    if(!*T)                         /*如果不存在值為x的資料元素,則返回0*/
        return 0;
    else
    {
        if(x.key==(*T)->data.key)           /*如果找到值為x的資料元素,則刪除該結點*/
            DeleteNode(T);
        else if((*T)->data.key>x.key)   /*如果當前元素值大於x的值,則在該結點的左子樹中查詢並刪除之*/
            BSTDelete(&(*T)->lchild,x);
        else                        /*如果當前元素值小於x的值,則在該結點的右子樹中查詢並刪除之*/
            BSTDelete(&(*T)->rchild,x);
        return 1;
    }
}


void DeleteNode(BiTree *s)
/*從二叉排序樹中刪除結點s,並使該二叉排序樹性質不變*/
{ 
    BiTree q,x,y;
    if(!(*s)->rchild)       /*如果s的右子樹為空,則使s的左子樹成為被刪結點雙親結點的左子樹*/
    {
        q=*s;
        *s=(*s)->lchild;
        free(q);
    }
    else if(!(*s)->lchild)  /*如果s的左子樹為空,則使s的右子樹成為被刪結點雙親結點的左子樹*/
    {
        q=*s;
        *s=(*s)->rchild;
        free(q);
    }
    else                    
    /*如果s的左、右子樹都存在,則使s的直接前驅結點代替s,並使其直接前驅結點的左子樹成為其雙親結點的右子樹結點*/
    {
        x=*s;
        y=(*s)->lchild;
        while(y->rchild)    /*查詢s的直接前驅結點,y為s的直接前驅結點,x為y的雙親結點*/
        {
            x=y;
            y=y->rchild;
        }
        (*s)->data=y->data;         /*結點s被y取代*/
        if(x!=*s)               /*如果結點s的左孩子結點不存在右子樹*/
            x->rchild=y->lchild; /*使y的左子樹成為x的右子樹*/
        else                    /*如果結點s的左孩子結點存在右子樹*/
            x->lchild=y->lchild;    /*使y的左子樹成為x的左子樹*/
        free(y);
    }
}

  通過呼叫Delete(T)來完成刪除當前結點的操作,而函式BSTDelete(&(*T)->lchild,x)和BSTDelete(&*T->rchild,x)則是實現在刪除結點後,利用引數T->lchild和T->rchild完成連線左子樹和右子樹,使二叉排序樹性質保持不變。

5.二叉排序樹應用舉例

  利用二叉樹的插入演算法建立一棵二叉排序樹,然後輸入一個元素,查詢該元素是否存在,最後輸出這棵樹的中序序列。

#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
void DeleteNode(BiTree *s);
int BSTDelete(BiTree *T,DataType x);
void InOrderTraverse(BiTree T);
BiTree BSTSearch(BiTree T,DataType x);
int BSTInsert(BiTree *T,DataType x);
void main()
{
    BiTree T=NULL,p;
    DataType table[]={55,43,66,88,18,80,33,21,72};
    int n=sizeof(table)/sizeof(table[0]);
    DataType x={80},s={18};
    int i;
    for(i=0;i<n;i++)
        BSTInsert(&T,table[i]);
    printf("關鍵字序列為:");
    for(i=0;i<n;i++)
        printf("%4d",table[i]);
    printf("\n");
    printf("中序遍歷二叉排序樹得到的序列為:\n");
    InOrderTraverse(T);
    p=BSTSearch(T,x);
    if(p!=NULL)
        printf("\n二叉排序樹查詢:元素關鍵字%d查詢成功.\n",x);
    else
        printf("\n二叉排序樹查詢:沒有找到元素%d.\n",x);
    BSTDelete(&T,s);
    printf("刪除元素%d後,中序遍歷二叉排序樹得到的序列為:\n",s.key);
    InOrderTraverse(T);
    printf("\n");
}
void InOrderTraverse(BiTree T)
/*中序遍歷二叉排序樹的遞迴實現*/
{
    if(T)                               /*如果二叉排序樹不為空*/
    {
        InOrderTraverse(T->lchild);     /*中序遍歷左子樹*/
        printf("%4d",T->data);          /*訪問根結點*/
        InOrderTraverse(T->rchild);         /*中序遍歷右子樹*/
    }
}
  • 測試結果

這裡寫圖片描述

相關文章