二叉搜尋樹的查詢、插入與刪除操作(Binary Search Tree, Search, Insert, Delete)(C++)
一、概念
設 x 是二叉搜尋樹中的一個結點。如果 y 是 x 左子樹中的一個結點,那麼 y.key ≦ x.key。如果 y 是 x 右子樹中的一個結點,那麼 y.key ≧ x.key。
為了便於描述,我們按如下方式定義樹結點:
struct TreeNode { int key; TreeNode* left; TreeNode* right; TreeNode* parent; TreeNode(int key, TreeNode* left = nullptr, TreeNode* right = nullptr, TreeNode* parent = nullptr) : key(key) , left(left) , right(right) , parent(parent) {} };
二、查詢二叉搜尋樹
二叉搜尋樹的查詢操作包括 Search, Minimum, Maximum, Successor, Predecessor,在任何高度為 h 的二叉搜尋樹上,所有的查詢操作時間複雜度均為 O(h)。
1. 查詢
輸入一個指向樹根結點的指標與一個關鍵字 key ,如果此節點存在,則返回一個指向關鍵字 key 的結點的指標,否則返回 null。遞迴與迭代版本分別如下:
TreeNode* treeSearch(TreeNode* root, int key) { if(root == nullptr || key == root->key){ return root; } if(key < root->key){ return treeSearch(root->left, key); }else{ return treeSearch(root->right, key); } }
TreeNode* iterativeTreeSearch(TreeNode* root, int key)
{
while(root && key != root->key){
if(key < root->key){
root = root->left;
}else{
root = root->right;
}
}
return root;
}
2. 最大關鍵字與最小關鍵字
通過從樹根開始沿著 left 孩子指標直到遇到一個 null,我們可以得到最小元素,反之可以得到最大元素。
TreeNode* treeMinimum(TreeNode* root)
{
while(root->left){
root = root->left;
}
return root;
}
TreeNode* treeMaximum(TreeNode* root)
{
while(root->right){
root = root->right;
}
return root;
}
3. 後繼與前驅
一個結點 x 的後繼是大於 x.key 的最小關鍵字的結點,前驅概念與之對稱。TreeNode* treeSuccessor(TreeNode* node)
{
if(node->right){
return treeMinimum(node->right);
}
TreeNode* y = node->parent;
while(y && node == y->right){
node = y;
y = y->parent;
}
return y;
}
TreeNode* treePredecessor(TreeNode* node)
{
if(node->left){
return treeMaximum(node->left);
}
TreeNode* y = node->parent;
while(y && node == y->left){
node = y;
y = y->parent;
}
return y;
}
三、插入和刪除
1. 插入
插入新元素後要保證二叉搜尋樹性質仍然成立,下圖展示了元素 13 插入樹的整個過程,紅色代表從樹根開始向下插入到資料項位置的簡單路徑,虛線表示了為插入資料項而加入的樹中的一條鏈。
程式碼如下:
void treeInsert(TreeNode* root, TreeNode* node)
{
TreeNode* y = nullptr;
TreeNode* x = root;
while(x){
y = x;
if(node->key < x->key){
x = x->left;
}else{
x = x->right;
}
}
node->parent = y;
if(y == nullptr){
root = node;
}else if(node->key < y->key){
y->left = node;
}else{
y->right = node;
}
}
2. 刪除
刪除操作較插入操作繁瑣,大致分為四種情況,如下圖所示:
(a)結點 z 沒有左孩子,用其右孩子 r 來替換 z,其中 r 可以是 NIL,也可以不是;
(b)結點 z 有一個左孩子 l 但沒有右孩子,用 l 來替換 z;
(c)結點 z 有兩個孩子,其左孩子是結點 l,其右孩子 y 還是其後繼,y 的右孩子是結點 x,用 y 替換 z,修改使 l 成為 y 的左孩子,但保留 x 仍為 y 的右孩子;
(d)結點 z 有兩個孩子(左孩子 l,右孩子 r),並且 z 的後繼 y ≠ r 位於以 r 為根的子樹中,用 y 自己的右孩子 x 來代換 y,並且置 y 為 r 的雙親,然後,再置 y 為 q 的孩子和 l 的雙親。
為了在二叉搜尋樹內移動子樹,定義一個子過程 Transpalnt,它是用一棵子樹替換一棵子樹併成為其雙親的孩子結點,當 Transplant 用一棵以 v 為根的子樹來替換一棵以 u 為根的子樹時,結點 u 的雙親就變為 v 的雙親,並且最後 v 成為 u 的雙親的相應孩子。其程式碼如下:
void transplant(TreeNode* root, TreeNode* oldNode, TreeNode* newNode)
{
if(oldNode->parent == nullptr){
root = newNode;
}else if(oldNode == oldNode->parent->left){
oldNode->parent->left = newNode;
}else{
oldNode->parent->right = newNode;
}
if(newNode){
newNode->parent = oldNode->parent;
}
}
於是整個刪除過程如下所示:
void treeDelete(TreeNode* root, TreeNode* node)
{
if(node->left == nullptr){
transplant(root, node, node->right);
}else if(node->right == nullptr){
transplant(root, node, node->left);
}else{
TreeNode* y = treeMinimum(node->right);
if(y->parent != node){
transplant(root, y, y->right);
y->right = node->right;
y->right->parent = y;
}
transplant(root, node, y);
y->left = node->left;
y->left->parent = y;
}
}