二叉排序樹


二叉排序樹又稱“二叉查找樹”、“二叉搜索樹”。

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

1. 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;

2. 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;

3. 它的左、右子樹也分別為二叉排序樹。

 

二叉排序樹通常采用二叉鏈表作為存儲結構。中序遍歷二叉排序樹可得到一個依據關鍵字的有序序列,一個無序序列可以通過構造一棵二叉排序樹變成一個有序序列,構造樹的過程即是對無序序列進行排序的過程。每次插入的新的結點都是二叉排序樹上新的葉子結點,在進行插入操作時,不必移動其它結點,只需改動某個結點的指針,由空變為非空即可。搜索、插入、刪除的時間復雜度等於樹高,期望O(logn),最壞O(n)(數列有序,樹退化成線性表,如右斜樹)。

/* 二叉樹的二叉鏈表結點結構定義 */
typedef  struct BiTNode    /* 結點結構 */
{
    int data;    /* 結點數據 */
    struct BiTNode *lchild, *rchild; /* 左右孩子指針 */
} BiTNode, *BiTree;

雖然二叉排序樹的最壞效率是O(n),但它支持動態查找,且有很多改進版的二叉排序樹可以使樹高為O(logn),如AVL、紅黑樹等。

二元排序樹的查找算法

在二元排序樹b中查找x的過程為:

 1.若b是空樹,則搜索失敗,否則:

 2.若x等於b的根節點的數據域之值,則查找成功;否則:

 3.若x小於b的根節點的數據域之值,則搜索左子樹;否則:

 4.查找右子樹。

算法實現:

/* 遞歸查找二叉排序樹T中是否存在key, */
/* 指針f指向T的雙親,其初始調用值為NULL */
/* 若查找成功,則指針p指向該數據元素結點,並返回TRUE */
/* 否則指針p指向查找路徑上訪問的最后一個結點並返回FALSE */
Status SearchBST(BiTree T, int key, BiTree f, BiTree *p) 
{  
    if (!T)    /*  查找不成功 */
    { 
        *p = f;  
        return FALSE; 
    }
    else if (key==T->data) /*  查找成功 */
    { 
        *p = T;  
        return TRUE; 
    } 
    else if (key<T->data) 
        return SearchBST(T->lchild, key, T, p);  /*  在左子樹中繼續查找 */
    else  
        return SearchBST(T->rchild, key, T, p);  /*  在右子樹中繼續查找 */
}

二叉排序樹的插入算法

利用查找函數,將關鍵字放到樹中的合適位置。

/*  當二叉排序樹T中不存在關鍵字等於key的數據元素時, */
/*  插入key並返回TRUE,否則返回FALSE */
Status InsertBST(BiTree *T, int key) 
{  
    BiTree p,s;
    if (!SearchBST(*T, key, NULL, &p)) /* 查找不成功 */
    {
        s = (BiTree)malloc(sizeof(BiTNode));
        s->data = key;  
        s->lchild = s->rchild = NULL;  
        if (!p) 
            *T = s;            /*  插入s為新的根結點 */
        else if (key<p->data) 
            p->lchild = s;    /*  插入s為左孩子 */
        else 
            p->rchild = s;  /*  插入s為右孩子 */
        return TRUE;
    } 
    else 
        return FALSE;  /*  樹中已有關鍵字相同的結點,不再插入 */
}

二叉排序樹的刪除算法

在二叉排序樹中刪去一個結點,分三種情況討論:

 1.若*p結點為葉子結點,即PL(左子樹)和PR(右子樹)均為空樹。由於刪去葉子結點不破壞整棵樹的結構,則只需修改其雙親結點的指針即可。

 2.若*p結點只有左子樹PL或右子樹PR,此時只要令PL或PR直接成為其雙親結點*f的左子樹(當*p是左子樹)或右子樹(當*p是右子樹)即可,作此修改也不破壞二叉排序樹的特性。

 3.若*p結點的左子樹和右子樹均不空。在刪去*p之后,為保持其它元素之間的相對位置不變,可按中序遍歷保持有序進行調整。比較好的做法是,找到*p的直接前驅(或直接后繼)*s,用*s來替換結點*p,然后再刪除結點*s。

 

/* 若二叉排序樹T中存在關鍵字等於key的數據元素時,則刪除該數據元素結點, */
/* 並返回TRUE;否則返回FALSE。 */
Status DeleteBST(BiTree *T,int key)
{ 
    if(!*T) /* 不存在關鍵字等於key的數據元素 */ 
        return FALSE;
    else
    {
        if (key==(*T)->data) /* 找到關鍵字等於key的數據元素 */ 
            return Delete(T);
        else if (key<(*T)->data)
            return DeleteBST(&(*T)->lchild,key);
        else
            return DeleteBST(&(*T)->rchild,key);
         
    }
}

/* 從二叉排序樹中刪除結點p,並重接它的左或右子樹。 */
Status Delete(BiTree *p)
{
    BiTree q,s;
    if((*p)->rchild==NULL) /* 右子樹空則只需重接它的左子樹(待刪結點是葉子也走此分支) */
    {
        q=*p; *p=(*p)->lchild; free(q);
    }
    else if((*p)->lchild==NULL) /* 只需重接它的右子樹 */
    {
        q=*p; *p=(*p)->rchild; free(q);
    }
    else /* 左右子樹均不空 */
    {
        q=*p; s=(*p)->lchild;
        while(s->rchild) /* 轉左,然后向右到盡頭(找待刪結點的前驅) */
        {
            q=s;
            s=s->rchild;
        }
        (*p)->data=s->data; /*  s指向被刪結點的直接前驅(將被刪結點前驅的值取代被刪結點的值) */
        if(q!=*p)
            q->rchild=s->lchild; /*  重接q的右子樹 */ 
        else
            q->lchild=s->lchild; /*  重接q的左子樹 */
        free(s);
    }
    return TRUE;
}

二叉排序樹性能分析

每個結點的Ci為該結點的層次數。最好的情況是二叉排序樹的形態和折半查找的判定樹相同,其平均查找長度和logn成正比(O(log2(n)))。最壞情況下,當先后插入的關鍵字有序時,構成的二叉排序樹為一棵斜樹,樹的深度為n,其平均查找長度為(n + 1) / 2。也就是時間復雜度為O(n),等同於順序查找。因此,如果希望對一個集合按二叉排序樹查找,最好是把它構建成一棵平衡的二叉排序樹(平衡二叉樹)。

Reference:

[1] 《大話數據結構》

[2] wikipedia: 二叉查找樹


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM