二叉搜索树

发布于:2025-03-11 ⋅ 阅读:(20) ⋅ 点赞:(0)

1.二叉搜索树的概念

⼆叉搜索树⼜称⼆叉排序树,它或者是⼀棵空树,或者是具有以下性质的⼆叉树 

  1. 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值
  2. 若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值
  3. 它的左右⼦树也分别为⼆叉搜索树 
  4. ⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义

一句话描述就是:左节点的值小于根右节点的值大于根 

如图:

 这里不建议将它称作搜索二叉树,因为。。。

//二叉搜索树

BinarySearchTree   //简称BSTree

//搜索二叉树

SearchBinaryTree   //简称XXTree(懂得都懂)

2.二叉搜索树的性能分析 

最优情况下,⼆叉搜索树为完全⼆叉树(或者接近完全⼆叉树),其⾼度为: logN

最差情况下,⼆叉搜索树退化为单⽀树(或者类似单⽀),其⾼度为: N

所以综合⽽⾔⼆叉搜索树增删查改时间复杂度为: O(N)

(为避免出现最差情况,需要对二叉树进行变形)

另外,根据二叉搜索树的特性,很容易联想到二分查找,二分查找也能实现O(logN)的查找效率

但是它有两大缺点:

  1. 需要存储在⽀持下标随机访问的结构中,并且有序
  2. 插⼊和删除数据效率很低,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数据

 而二叉搜索树数据插入时就已经实现了排序,并且由于使用指针指向节点的方式,删除效率也很高

3.二叉搜索树的插入 

数据插入的具体过程:

  •  树为空,则直接新增结点,赋值给root指针
  • 树不空,按⼆叉搜索树性质,插⼊值⽐当前结点⼤往右⾛,插⼊值⽐当前结点⼩往左⾛,找到空位 置,插⼊新结点
  • 如果⽀持插⼊相等的值,插⼊值跟当前结点相等的值可以往右⾛,也可以往左⾛,找到空位置,插⼊新结点 (要保持逻辑⼀致性,插⼊相等的值不要⼀会往右⾛,⼀会往左⾛)

比如在下面这棵二叉搜索树插入一个16: 

 

在此时若要插入16, 根节点8,不为空,并且16 > 8,向右子节点走,

节点10不为空,16 > 10,向右子节点走,

节点14不为空,16 > 14,向右子节点走,

此时走到空,在此处插入16

4.二叉搜索树的查找 

  1. 从根开始⽐较,查找x,x⽐根的值⼤则往右边⾛查找,x⽐根值⼩则往左边⾛查找
  2. 最多查找⾼度次,⾛到到空,还没找到,这个值不存在
  3. 如果不⽀持插⼊相等的值,找到x即可返回
  4. 如果⽀持插⼊相等的值,意味着有多个x存在,⼀般要求查找中序的第⼀个x。(如下图,查找3,要 找到1的右孩⼦的那个3返回 

5.⼆叉搜索树的删除 

删除过程

 ⾸先查找元素是否在⼆叉搜索树中,如果不存在,则返回false

如果查找元素存在则分以下四种情况分别处理:(假设要删除的结点为N)

  1. 要删除结点N左右孩⼦均为空
  2. 要删除的结点N左孩⼦为空,右孩⼦结点不为空
  3. 要删除的结点N右孩⼦为空,左孩⼦结点不为空
  4. 要删除的结点N左右孩⼦结点均不为空

对应以上四种情况的解决⽅案:

  1. 把N结点的⽗亲对应孩⼦指针指向空,直接删除N结点
  2. 把N结点的⽗亲对应孩⼦指针指向N的右孩⼦,直接删除N结点
  3. 把N结点的⽗亲对应孩⼦指针指向N的左孩⼦,直接删除N结点
  4. ⽆法直接删除N结点,因为N的两个孩⼦⽆处安放,只能⽤替换法删除。找N左⼦树的值最⼤结点 R(最右结点)或者N右⼦树的值最⼩结点R(最左结点)替代N,因为这两个结点中任意⼀个,放到N的 位置,都满⾜⼆叉搜索树的规则

举例子:

删除节点1、4、7、13,这几个节点的删除都满足情况1。可直接删除,相应的父节点对这个节点的指向为NULL

删除节点10,满足情况2。将父节点对该节点的指向改为指向其右子节点

删除节点14,满足情况3。将父节点对该节点的指向改为指向其左子节点

删除节点3、6、8,满足情况4。以删除节点8为例,其左右子节点都不为空

方法1:将左子树的最右节点7代替节点8的位置

方法2:将右子树的最左节点10代替节点8的位置

 6.二叉搜索树的实现

template<class K>
struct BSTNode
{
    K _key;
    BSTNode<K>* _left;
    BSTNode<K>* _right;
    BSTNode(const K& key)
        :_key(key)
        , _left(nullptr)
        , _right(nullptr)
    {}
};
template<class K>
class BSTree
{
    typedef BSTNode<K> Node;
public:
    //插入
    bool Insert(const K& key)
    {
        if (_root == nullptr)
        {
            _root = new Node(key);
            return true;
        }
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {
            if (cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (cur->_key > key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                return false;
            }
        }
        cur = new Node(key);
        if (parent->_key < key)
        {
            parent->_right = cur;
        }
        else
        {
            parent->_left = cur;
        }
        return true;
    }
    //查找
    bool Find(const K& key)
    {
        Node* cur = _root;
        while (cur)
        {
            if (cur->_key < key)
            {
                cur = cur->_right;
            }
            else if (cur->_key > key)
            {
                cur = cur->_left;
            }
            else
            {
                return true;
            }
        }
        return false;
    }
    //删除
    bool Erase(const K& key)
    {
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {
            if (cur->_key < key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (cur->_key > key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                // 0-1个孩⼦的情况
                //删除情况1 2 3均可以直接删除,改变⽗亲对应孩⼦指针指向即可

                if (cur->_left == nullptr)
                {
                    if (parent == nullptr)
                    {
                        _root = cur->_right;
                    }
                    else
                    {
                        if (parent->_left == cur)
                            parent->_left = cur->_right;
                        else
                            parent->_right = cur->_right;
                    }
                    delete cur;
                    return true;
                }
                else if (cur->_right == nullptr)
                {
                    if (parent == nullptr)
                    {
                        _root = cur->_left;
                    }
                    else
                    {
                        if (parent->_left == cur)
                            parent->_left = cur->_left;
                        else
                            parent->_right = cur->_left;
                    }
                    delete cur;
                    return true;
                }
                else
                {
                    // 2个孩⼦的情况
                    //删除情况4,替换法删除
                    //假设这⾥我们取右⼦树的最⼩结点作为替代结点去删除
                    //这⾥尤其要注意右⼦树的根就是最⼩情况的情况的处理,对应课件图中删除8的情况
                    //⼀定要把cur给rightMinP,否会报错
                    Node* rightMinP = cur;
                    Node* rightMin = cur->_right;
                    while (rightMin->_left)
                    {
                        rightMinP = rightMin;
                        rightMin = rightMin->_left;
                    }
                    cur->_key = rightMin->_key;

                    if (rightMinP->_left == rightMin)
                        rightMinP->_left = rightMin->_right;
                    else
                        rightMinP->_right = rightMin->_right;

                    delete rightMin;
                    return true;
                }
            }
        }
        return false;
    }
    void InOrder()
    {
        _InOrder(_root);
        cout << endl;
    }
private:
    void _InOrder(Node* root)
    {
        if (root == nullptr)
        {
            return;
        }
        _InOrder(root->_left);
        cout << root->_key << " ";
        _InOrder(root->_right);
    }
    Node* _root = nullptr;
};

7.相关容器

map/set/multimap/multiset系列容器底层就是⼆叉搜索树 

  • map/set不⽀持插⼊相等值
  • multimap/multiset⽀持插⼊相等值