📜  数据结构-二进制搜索树(1)

📅  最后修改于: 2023-12-03 15:26:09.184000             🧑  作者: Mango

数据结构-二叉搜索树

简介

二叉搜索树(Binary Search Tree,简称BST)是一种常见的树型数据结构,也是一种基础的搜索算法。BST中,每个节点都只有最多两个子节点,并且左子树上所有的节点的值均小于该节点的值,右子树上所有节点的值均大于该节点。通过这种方式构建的二叉树,能够支持快速的查找、插入、删除操作。

实现原理

BST的实现需要满足以下性质:

  1. 结点的左子树中只包含小于结点的值。
  2. 结点的右字树中只包含大于结点的值。
  3. 左右子树都必须是二叉搜索树。

通过满足这些性质,我们可以保证在每次操作时,都可以通过比较目标值和节点值的大小来决定向左还是向右搜索,从而加快搜索的速度。

实现方式

我们可以采用两种方式来实现BST,即指针实现和数组实现。

指针实现

指针实现方式实际上就是构建一棵树,每个节点都是一个指针。每个节点包含一个key和value,以及左右子节点的指针。插入、查找和删除时,都是通过比较key的大小来判断进入左子树还是右子树。

插入操作

BST中的插入操作非常简单,只要按照二叉搜索树的性质递归比较节点的大小,然后将其插入到正确的位置即可。

以下是一个示例代码:

class TreeNode {
public:
    TreeNode(int key, int value) : key(key), value(value), left(nullptr), right(nullptr) {}
    int key;
    int value;
    TreeNode* left;
    TreeNode* right;
};
class BST {
public:
    void insert(int key, int value) {
        root = insert(root, key, value);
    }
private:
    TreeNode* insert(TreeNode* root, int key, int value) {
        if (!root) {
            return new TreeNode(key, value);
        }
        if (key > root->key) {
            root->right = insert(root->right, key, value);
        } else if (key < root->key) {
            root->left = insert(root->left, key, value);
        }
        return root;
    }
    TreeNode* root;
};

查询操作

查询操作与插入操作的实现方式类似,同样是通过比较key的大小,递归查找节点。

以下是一个示例代码:

class BST {
public:
    int find(int key) {
        TreeNode* node = find(root, key);
        if (!node) {
            return -1;
        }
        return node->value;
    }
private:
    TreeNode* find(TreeNode* root, int key) {
        if (!root) {
            return nullptr;
        }
        if (key > root->key) {
            return find(root->right, key);
        } else if (key < root->key) {
            return find(root->left, key);
        } else {
            return root;
        }
    }
    TreeNode* root;
};

删除操作

删除操作是BST中比较难实现的,需要考虑多种情况,包括3种情况:

  1. 要删除的节点只有左子树或右子树,可以直接删除,将其子树提升为父节点的子树。
  2. 要删除的节点既有左子树也有右子树,选取该节点左子树中最大的节点或右子树中最小的节点,将其值赋给目标节点,然后递归删除最大或者最小节点即可。
  3. 要删除的节点是叶子节点,直接删除即可。

以下是一个示例代码:

class BST {
public:
    void remove(int key) {
        root = remove(root, key);
    }
private:
    TreeNode* remove(TreeNode* root, int key) {
        if (!root) {
            return nullptr;
        }
        if (key > root->key) {
            root->right = remove(root->right, key);
        } else if (key < root->key) {
            root->left = remove(root->left, key);
        } else {
            if (!root->left) {
                auto temp = root->right;
                delete root;
                return temp;
            } else if (!root->right) {
                auto temp = root->left;
                delete root;
                return temp;
            } else {
                auto temp = root->left;
                while (temp->right) {
                    temp = temp->right;
                }
                root->key = temp->key;
                root->value = temp->value;
                root->left = remove(root->left, temp->key);
            }
        }
        return root;
    }
    TreeNode* root;
};
数组实现

我们还可以采用数组的方式来实现BST,数组实现的好处是可以节约空间,避免使用指针和动态内存分配。我们可以按照层序遍历的方式来将二叉搜索树建立在一个数组中。

具体实现的方式是:假设根节点在数组下标0,那么对于任意一个节点i,左子节点在2i+1下标处,右子节点在2i+2下标处。

下面是一个数组实现的示例代码:

class BST {
public:
    void insert(int key, int value) {
        if (root.empty()) {
            root.resize(1, {-1, -1, -1});
            root[0] = {key, value, -1};
        } else {
            int i = 0;
            while (true) {
                if (key > root[i].key) {
                    if (root[i].right == -1) {
                        root[i].right = root.size();
                        root.emplace_back(key, value, -1);
                        break;
                    }
                    i = root[i].right;
                } else if (key < root[i].key) {
                    if (root[i].left == -1) {
                        root[i].left = root.size();
                        root.emplace_back(key, value, -1);
                        break;
                    }
                    i = root[i].left;
                } else {
                    root[i].value = value;
                    break;
                }
            }
        }
    }
    int find(int key) {
        int i = 0;
        while (i < root.size() && root[i].key != -1) {
            if (key > root[i].key) {
                i = root[i].right;
            } else if (key < root[i].key) {
                i = root[i].left;
            } else {
                return root[i].value;
            }
        }
        return -1;
    }
    void remove(int key) {
        auto find_node = [&]() -> int {
            int i = 0;
            while (i < root.size() && root[i].key != -1) {
                if (key > root[i].key) {
                    i = root[i].right;
                } else if (key < root[i].key) {
                    i = root[i].left;
                } else {
                    return i;
                }
            }
            return -1;
        };
        int idx = find_node();
        if (idx == -1) {
            return;
        }
        if (root[idx].left == -1 && root[idx].right == -1) {
            root[idx] = {-1, -1, -1};
        } else if (root[idx].left == -1) {
            root[idx] = root[root[idx].right];
            remove(root[idx].key);
        } else if (root[idx].right == -1) {
            root[idx] = root[root[idx].left];
            remove(root[idx].key);
        } else {
            int i = root[idx].left;
            while (root[i].right != -1) {
                i = root[i].right;
            }
            root[idx] = root[i];
            remove(root[i].key);
        }
    }
private:
    struct Node {
        int key, value, left, right;
    };
    std::vector<Node> root;
};
总结

二叉搜索树是一种非常常见的数据结构,它能够快速地进行插入、查找和删除操作。我们可以采用指针和数组两种方式来实现BST。指针实现更加直接,代码也更加简洁,但是对于大规模的数据,动态内存分配的开销比较大。数组实现则可以避免这个问题,但是在插入和删除操作时需要进行数组的扩容和缩容操作,比较繁琐。在实际开发中,我们根据实际需要选择更加适合的方式来实现。