二叉搜索树增删节点《算法导论》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
*/

猜你喜欢

转载自www.cnblogs.com/meixiaogua/p/9884397.html