向二叉搜索树增加一个节点是比较简单的,每个新节点都会成为树上的新叶子,我们要做的是从根开始,沿着一条路径,抵达安放新叶子的正确位置。这条路径是怎样找到的呢?
路径的起点自然是根节点了,把起点作为当前节点,和新节点比较大小,如果新节点较小,那么新节点应该属于当前节点的左子树,于是选择当前节点的左孩子作为新的当前节点,否则选择其右孩子。直到当前节点为空节点为止,那便是要安放新节点的位置了。
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左右孩子的情况有下面四种:
没有孩子,此时只需要用一个空节点将x节点替换下来就可以了,其实可以理解为让一个空节点接管x节点的父亲节点。
只有左孩子,就用左子树替换下来x。其他节点不用修改。
只有右孩子,也一样,用右子树替换下来x。
比较麻烦的是同时有两个孩子,它们满足一个条件“左子树都小于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
*/