17.二叉排序樹的查找、插入、刪除操作


/*
8.6 二叉排序樹:在創建樹的時候就構建一個有序的樹
特點:
1.若它的左子樹不空,則左子樹上所有結點的值均小於它的根結構的值;
2.若它的右子樹不空,則右子樹上所有結點的值均大於它的根節點的值;
3.它的左、右子樹也分別為二叉排序樹

構建一顆二叉排序樹的目的,其實並不是為了排序,而是為了提高查找和插入刪除關鍵字的速度。不管怎么說,在一個有序數據集上的查找,
速度總是要快於無序的數據集的,而二叉排序樹這種非線性的結構,也有利於插入和刪除的實現。
*/
/*
8.6.1 二叉排序樹查找操作
首先我們提供一個二叉樹結構
*/
typedef struct BiTNode
{
    //結點數據
    int data;
    //左右孩子指針
    struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
//然后我們來看看二叉排序樹的查找是如何實現的

/*
遞歸查找二叉排序樹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);    //在右子樹繼續查找
}

/*8.6.2 二叉排序樹插入操作
有了二叉排序樹的查找函數,那么所謂的二叉排序樹的插入,其實也就是將關鍵字放到樹中合適位置而已。

當二叉排序樹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)
            //插入s為新的根節點
            *T = s;
        else if(key < p->data)
            //插入s為左孩子
            p->lchild = s;
        else
            //插入孩子為右孩子
            p->rchild = s;
        return TRUE;
    }
    else
        //樹中已有關鍵字相同的結點,不再插入
        return FALSE;    
}

/*
8.6.3 二叉樹刪除操作
俗話說“請神容易送神難”,我們已經介紹了二叉排序樹的查找與插入算法,但是對於二叉排序樹的刪除,就不那么容易,
我們不能因為刪除了結點,而讓這棵樹變得不滿足二叉樹排序的特性,所以刪除需要考慮多種情況。
*/

/*
根據我們對刪除結點三種情況的分析:
1.葉子結點;
2.僅有左或右子樹的結點;
3.左右子樹都有結點,我們來看代碼,下面這個算法是遞歸方式對二叉排序樹T查找key,查找到時刪除。
*/
/*
若二叉排序樹T中存在關鍵字等於key的數據元素時,則刪除該數據元素結點,並返回TRUE;否則返回FALSE
*/
Status DeleteBST(BiTree *T, int key)
{
    //不存在關鍵字等於key的數據元素
    if(!*T)
        return FALSE;
    else
    {
        //找到關鍵字等於key的數據元素
        if (key == (*T)->data)
            return Delete(T);
        else if (key < (*T)->data)
            return DeleteBST(&(*T)->lchild, key);
        else
            return DeleteBST(&(*T)->rchild, key);
    }
}
/*
上邊這段代碼和前面的二叉排序樹查找幾乎完全相同,唯一的區別就在於第8行,此時執行的時Delete方法,
對當前結點進行三處操作。我們來看Delete的代碼。
*/
//從二叉排序樹中刪除結點p,並重接它的左或右子樹。
Status Delete(BiTree *p)
{
    BiTree q,s;
    //右子樹空則只需重接它的左子樹
    if ((*p)->rchild == NULL)
    {
        q = *p;
        *p = (*p)->lchild;
        free(q);
    }
    //左子樹為空則只需重接它的右子樹
    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;
        }
        //s指向被刪除結點的直接前驅
        (*p)->data = s->data;
        if (q != *p)
            //重接q的右子樹
            q->rchild = s->lchild;
        else
            //重接q的左子樹
            q->lchild = s->lchild;
        free(s);
    }
    return TRUE;
}

/*
8.6.4 二叉排序樹總結
總之,二叉排序樹是以鏈接的方式存儲,保持了鏈接存儲結構在執行插入或刪除操作時不用移動元素的優點,
只要找到合適的插入和刪除位置后,僅需修改鏈接指針即可。插入刪除的時間性能比較好。而對於二叉排序樹
的查找,走的就是從根節點到要查找的結點的路徑,其比較次數等於給定值的結點在二叉排序樹的層數。極端
情況,最少為1次,即根節點就是要找的結點,最多也不會超過樹的深度。也就是說,二叉排序樹的查找性能
取決於二叉樹的形狀。可問題就在於,二叉排序樹的形狀是不確定的,有可能時極端的左傾或者右傾。
    也就是說,我們希望二叉排序樹是平衡的,即其深度與完全二叉樹相同,均為,那么查找的時間復雜度
也就是O(logn),近似於折半查找,事實上左圖也不夠平衡(p580 圖8-6-18),明顯的左重右輕。
    不平衡的最壞情況就是像p580 圖8-6-18 右圖的斜樹,查找時間復雜度為O(n),這等同於順序查找。
    因此,如果我們希望對一個集合按二叉排序樹查找,做好是把它們構建成一顆平衡的二叉排序樹。這樣我們就
    引申出另一個問題,如何讓二叉排序樹平衡的問題。
*/

 


免責聲明!

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



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