1. 程式人生 > >二叉搜尋樹的查詢、插入與刪除操作(Binary Search Tree, Search, Insert, Delete)(C++)

二叉搜尋樹的查詢、插入與刪除操作(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;
    }
}