二叉搜尋樹增刪節點《演算法導論》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左右孩子的情況有下面四種:
沒有孩子,此時只需要用一個空節點將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
*/