1. 程式人生 > >二叉搜尋樹增刪節點《演算法導論》12.3節

二叉搜尋樹增刪節點《演算法導論》12.3節

  • 向二叉搜尋樹增加一個節點是比較簡單的,每個新節點都會成為樹上的新葉子,我們要做的是從根開始,沿著一條路徑,抵達安放新葉子的正確位置。這條路徑是怎樣找到的呢?

    路徑的起點自然是根節點了,把起點作為當前節點,和新節點比較大小,如果新節點較小,那麼新節點應該屬於當前節點的左子樹,於是選擇當前節點的左孩子作為新的當前節點,否則選擇其右孩子。直到當前節點為空節點為止,那便是要安放新節點的位置了。

struct Node
{
    int   key;
    Node* parent;
    Node* left;
    Node* right;
    Node(int k) :key(k),parent(nullptr),
        left(nullptr),right(nullptr){}
    Node(){}
};

void insert(Node* pRoot, Node* pAdd)
{
    Node *pParent = pRoot, *pTmpParent;
    bool bLeft;
    while(true)
    {
        bLeft = (pAdd->key < pParent->key) ? true : false;
        pTmpParent = bLeft ? pParent->left : pParent->right;
        if(nullptr != pTmpParent)
        {
            pParent = pTmpParent;
            continue;
        }

        pAdd->parent = pParent;
        if(bLeft)
            pParent->left = pAdd;
        else
            pParent->right = pAdd;
        break;
    }
}
  • 從二叉搜尋樹刪除一個節點的計算稍顯複雜,因為樹上每個節點都肩負著最多兩個孩子的定址任務。如果待刪除的節點有孩子,它必須先把自己的孩子安排妥當才可以從容離開。

    怎樣算安排妥當呢?其他元素依然保留在樹上,且“二叉搜尋樹上每個節點的左孩子都比自己小,有孩子都比自己大”這個良好屬性不會因為某個節點的離開而遭到破壞。要想做好這兩點,就需要調整樹上某些節點的位置,一個節點與位置相關的屬性有父親指標和兩個孩子指標。所以我先封裝了三個方法,分別實現讓一個節點pDst接管另一個節點pSrc的父親、左孩子、右孩子指標。

void take_over_parent(Node*& pRoot, Node* pSrc, Node* pDst)
{
    Node* pParent = pSrc->parent;
    bool bLeft = false;
    if(pParent)
        bLeft = (pParent->left == pSrc);
    //父親認下新孩子
    if(pParent)
    {
        if(bLeft)
            pParent->left = pDst;
        else
            pParent->right = pDst;
    }
    else
    {
        pRoot = pDst;
    }
    //孩子認下新父親
    if(pDst)
    {
        pDst->parent = pParent;
    }
}

void take_over_left(Node* pSrc, Node* pDst)
{
    pDst->left = pSrc->left;
    pDst->left->parent = pDst;
}

void take_over_right(Node* pSrc, Node* pDst)
{
    pDst->right = pSrc->right;
    pDst->right->parent = pDst;
}
  • 待刪除節點x左右孩子的情況有下面四種:

    1. 沒有孩子,此時只需要用一個空節點將x節點替換下來就可以了,其實可以理解為讓一個空節點接管x節點的父親節點。

    2. 只有左孩子,就用左子樹替換下來x。其他節點不用修改。

    3. 只有右孩子,也一樣,用右子樹替換下來x。

    4. 比較麻煩的是同時有兩個孩子,它們滿足一個條件“左子樹都小於x,右子樹都大於x”,現在刪除x之後還需要選出一個節點頂替x的位置,這個節點要麼是左子樹的最大值,要麼是右子樹的最小值,才能滿足上面的條件。假設我選了右子樹的最小值y,y就成為了x的接班人。這個交接工作分四步進行:1. y把自己的右孩子託管給y的父親;2.y接管x的右孩子; 3. y接管x的左孩子; 4. y接管x的父親;在y本身就是x的右孩子的情況下,1、2兩步省略。

//從以pRoot為根的樹上刪除節點pDelete,如果刪除後樹上節點樹為0,則將pRoot置空。
void Delete(Node*& pRoot, Node* pDelete)
{
    Node* pLeft = pDelete->left;
    Node* pRight = pDelete->right;
    Node* pSuccessor = nullptr;
    if(!pLeft && !pRight)
        take_over_parent(pRoot, pDelete, nullptr);
    else if(pLeft && !pRight)
        take_over_parent(pRoot, pDelete, pLeft);
    else if(!pLeft && pRight)
        take_over_parent(pRoot, pDelete, pRight);
    else
    {
        pSuccessor = minimum(pRight);
        if(pSuccessor != pRight)
        {
            take_over_parent(pRoot, pSuccessor, pSuccessor->right);
            take_over_right(pDelete, pSuccessor);
        }
        take_over_left(pDelete, pSuccessor);
        take_over_parent(pRoot, pDelete, pSuccessor);
    }
    delete pDelete;
}

Node* minimum(Node* pRoot)
{
    while(pRoot->left != nullptr)
        pRoot = pRoot->left;
    return pRoot;
}
  • 下面附上測試程式碼

#include <iostream>
#include <stack>
using namespace std;

Node* build()
{
    Node* pRoot = new Node(15);
    insert(pRoot, new Node(6));
    insert(pRoot, new Node(3));
    insert(pRoot, new Node(2));
    insert(pRoot, new Node(4));
    insert(pRoot, new Node(7));
    insert(pRoot, new Node(13));
    insert(pRoot, new Node(9));
    insert(pRoot, new Node(18));
    insert(pRoot, new Node(17));
    insert(pRoot, new Node(20));
    return pRoot;
}

//destroy the tree
void destroy(Node* pRoot)
{
    std::queue<Node*> q;
    q.push(pRoot);
    Node* p;
    while(!q.empty())
    {
        p = q.front();
        q.pop();
        if(!p)
            continue;
        q.push(p->left);
        q.push(p->right);
        delete p;
    }
}

//inorder tree walk
void walk(Node* pRoot)
{
    stack<Node*> stk;
    stk.push(pRoot);
    while(true)
    {
        Node* pCurrent = stk.top();
        if(pCurrent)
        {
            stk.push(pCurrent->left);
            continue;
        }
        stk.pop();
        if(stk.empty())
            break;
        pCurrent = stk.top();
        cout << pCurrent->key << "\t";
        stk.pop();
        stk.push(pCurrent->right);
    }
}

void testDelete()
{
    Node* pRoot = build();
    walk(pRoot);
    cout << endl;
    Delete(pRoot, pRoot);
    walk(pRoot);
    cout << endl;
    Delete(pRoot, pRoot->right);
    walk(pRoot);
    cout << endl;
    Delete(pRoot, pRoot->right);
    walk(pRoot);
    cout << endl;
    destroy(pRoot);
}
/*output:
2   3   4   6   7   9   13  15  17  18  20
2   3   4   6   7   9   13  17  18  20
2   3   4   6   7   9   13  17  20
2   3   4   6   7   9   13  17
*/