二叉搜索树

介绍

二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树结构,适合用于实现高效的搜索、插入和删除操作

性质

节点结构

  • 每个节点都包含一个值(通常是一个可比较的数据元素)
  • 最多两个子节点,分别称为左子节点和右子节点
  • 左子节点的值小于当前节点的值,而右子节点的值大于当前节点的值
  • 所以一个结点的左子树里的数值一定比它小,右子树一定比他大

中序遍历

对BST进行中序遍历(In-Order Traversal)会以升序访问树中的所有节点

唯一性

BST中不允许重复的值,每个节点的值都必须是唯一的

左子树和右子树也是BST

BST的左子树和右子树本身也是BST

这个性质递归地应用于树的每个子树

但是,如果BST不平衡(即左右子树的高度差太大),它的性能可能会下降,因此需要采取措施来确保树的平衡

例如使用平衡二叉树(AVL树)或红黑树等

key版本实现

思路

由于搜索树的特性,我们需要按照它来进行相应的插入/删除/查找

并且搜索树的实现有两种方式 -- 递归 / 非递归

非递归

插入
  • 首先防止该树是空树
  • 然后就是,找到要插入结点应该在的位置
  • (要注意:因为树中无重复结点,所以当找到相等数值的点,直接返回false,而不执行插入)
  • 除此之外,还要注意:因为我们要改变的是结构体内的指针指向(也就是结点内的指向),所以需要该结点的指针(也就是上一个结点的指针),所以需要在找的过程中,父结点位置的指针也需要拿到

查找
  • 查找的思路很简单,因为搜索树有自己特定的结构,所以很容易查找
  • 当前结点比要查找的结点数值小时,就往它的右树走;大了就去左树
 
删除

首先就是要先找到要删除的结点

其次就是要判断

  • 如果该结点无子树,直接将它的父结点指空就行
  • 如果该结点仅有一个子树,直接让父结点指向它的子树
  • 这两种情况可以合并(因为无子树,也就相当于子树是空)

  • 并且要防止删除根结点的情况 -- 因为我们会使用父结点来连接它的子树
  • 但根结点没有父结点,所以需要特殊处理

接下来就是最麻烦的一种情况,该结点左右子树均存在

  • 一旦要将它删除,就需要找到这个结点的代替结点(要满足搜索树的结构特性)
  • 要么是该结点的左子树中的最大结点
  • 要么是右子树中的最小结点

我们这里寻找左子树的最大结点:

  • (最大结点,也就意味着它没有右子树)
  • 所以直接让它和删除结点互换数值后,父结点指向最大结点的左子树即可
  • 但是要注意,我们无法确定这个最大结点是在父结点的左还是右
  • (一般来说应该在右,但如果最大结点没有发生变化,那就是在左,初始值就是删除结点的左结点)

最后我们要删除的是找到的最大结点,所以需要将它的位置赋值给cur(最后统一释放cur)

递归 

注意
  • 要注意的是 -- 递归一般都需要传入结点指针参数,但是在类外使用该函数时,无法传入(因为根结点指针是私有成员)
  • 所以可以设计子函数,让子函数完成递归过程,父函数作为外部调用的接口
  • 递归函数的代码量一般都比非递归的少,因为实际的过程是由编译器来完成,我们只需要写出统一的方法即可
插入

子函数的使用:

  • 注意这里的root是引入!!!
  • 引入可有大用处了,可以直接赋值(这里是引用传入,直接可以修改本体;非递归那里是修改类内的成员变量/修改父结点的指向)
  • 然后通过搜索树的特性,去合适的地方找到插入的位置
  • (注意,这里root使用的时候,实际上它都是根结点的引用,或者父结点的左指针/右指针的引用,所以可以直接修改,而不用拿到父结点的指针)

查找

  • 查找逻辑就很简单,利用特性不断寻找就行,找到了就返回该指针,找到空就返回空
  • 要注意,在递归函数调用前,如果有返回值,最好都加上return,可以让函数一旦拿到返回值后,就一层层直接返回
删除

看着好像很多,但其实主要是注释,代码量比非递归的少太多了

  • 还是一样的,先查找
  • 当找到该结点时,还是要先判断有无左右子树
  • 如果无子树/仅有一边子树,可以直接让父结点改变指向(在这里就直接让root更新就行)
  • 因为root实际上是当前结点(根结点)/当前结点的父结点的左/右指针别名

  • 如果左右都有,还是一样的,先找到代替的结点
  • 但接下来的操作就不一样了,因为递归最重要的就是复用代码
  • 所以我们试着将这种情况 --> 可以被处理的无子树/仅有一边子树的情况
  • 可以先交换数值,那么我们就可以转变成在左子树中删除(原先要删除结点的数值)的结点
  • (如果不交换,咋找也没办法,必须得交换才行,交换了才能修改此时该结点左右子树的存在状态)
  • 然后进行递归,最终会在前两种情况下进行处理
  • 最后释放结点

 拷贝构造 

很简单的逻辑,就一层一层往下拷贝,遇到空/左右均拷贝完毕就返回到上一层

代码

namespace key
{
    template <class K>
    class BsTree_node
    {
    public:
        BsTree_node(const K &key)
            : _data(key), _left(nullptr), _right(nullptr) {}

        K _data;
        BsTree_node<K> *_left;
        BsTree_node<K> *_right;
    };

    template <class K>
    class BsTree
    {
        typedef BsTree_node<K> node;

    public:
        // 构造+析构+拷贝
        BsTree()
            : _root(nullptr) {}
        ~BsTree()
        {
            _destroy(_root);
        }
        BsTree(const BsTree<K> &t)
        {
            _root = copy(t._root);
        }
        BsTree<K> &operator=(BsTree<K> t)
        {
            std::swap(_root, t._root);
            return *this;
        }
        // 遍历
        void inOrder()
        {
            _inOrder(_root);
            std::cout << std::endl;
        }
        // 各种功能
        bool insert(const K &key);
        bool insert_R(const K &key)
        {
            return _insert_R(_root, key);
        }
        node *find(const K &key);
        node *find_R(const K &key)
        {
            return _find_R(_root, key);
        }
        bool erase(const K &key);
        bool erase_R(const K &key)
        {
            return _erase_R(_root, key);
        }

    private: // 所需的子函数
        void _destroy(node *root)
        {
            if (root == nullptr)
            {
                return;
            }
            _destroy(root->_left);
            _destroy(root->_right);
            delete root;
            root = nullptr;
        }
        void _inOrder(node *root) // 这里必须得传入参数,不然没法递归,但_root是私有成员,没法在类外调用,所以可以外部包装一层函数
        {
            if (root == nullptr)
            {
                return;
            }
            _inOrder(root->_left);
            std::cout << root->_data << " ";
            _inOrder(root->_right);
        }
        node *copy(node *root)
        {
            node *cp = nullptr;
            if (root == nullptr)
            {
                return nullptr;
            }
            cp = new node(root->_data);
            cp->_left = copy(root->_left);
            cp->_right = copy(root->_right);
            return cp;
        }

        bool _insert_R(node *&root, const K &key);
        node *_find_R(node *root, const K &key);
        bool _erase_R(node *&root, const K &key);

    private: // 成员
        node *_root;
    };

    // 函数实现
    template <class K>
    bool BsTree<K>::insert(const K &key)
    {
        if (_root == nullptr)
        {
            _root = new node(key);
        }
        else
        {
            node *cur = _root, *parent = cur;
            while (cur)
            {
                if (key > cur->_data)
                {
                    parent = cur;
                    cur = cur->_right;
                }
                else if (key < cur->_data)
                {
                    parent = cur;
                    cur = cur->_left;
                }
                else
                {
                    return false;
                }
            }
            if (key < parent->_data)
            {
                parent->_left = new node(key);
            }
            else
            {
                parent->_right = new node(key);
            }
        }
        return true;
    }
    template <class K>
    bool BsTree<K>::_insert_R(typename BsTree<K>::node *&root, const K &key)
    {
        if (root == nullptr)
        {                         // 该插入了
            root = new node(key); // 此时root是它父结点的左指针/右指针的别名,直接修改即可
            return true;
        }
        if (key > root->_data)
        {
            return _insert_R(root->_right, key); // return 是为了让该函数完成操作返回true/false时,可以快速出[递归出的一堆函数],因为只要返回值,当前函数就执行完成辽
        }
        else if (key < root->_data)
        {
            return _insert_R(root->_left, key);
        }
        else
        {
            return false;
        }
    }

    //
    template <class K>
    typename BsTree<K>::node *BsTree<K>::find(const K &key)
    {
        node *root = _root;
        while (root)
        {
            if (key > root->_data)
            {
                root = root->_right;
            }
            else if (key < root->_data)
            {
                root = root->_left;
            }
            else
            {
                return root;
            }
        }
        return nullptr;
    }
    template <class K>
    typename BsTree<K>::node *BsTree<K>::_find_R(typename BsTree<K>::node *root, const K &key)
    {
        if (root == nullptr)
        {
            return nullptr;
        }
        if (key > root->_data)
        {
            return _find_R(root->_right, key);
        }
        else if (key < root->_data)
        {
            return _find_R(root->_left, key);
        }
        else
        {
            return root;
        }
    }

    //
    template <class K>
    bool BsTree<K>::erase(const K &key)
    {
        node *cur = _root, *parent = cur;
        while (cur) // 找到删除的结点
        {
            if (key > cur->_data)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (key < cur->_data)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                break;
            }
        }
        if (cur == nullptr)
        {
            return false;
        }
        // 此时cur指向要被删除的结点
        if (cur->_left == nullptr) // cur左为空
        {
            if (cur == _root) // 防止cur此时是根结点
            {
                _root = cur->_right;
            }
            else
            {
                // 判断cur在parent的左边还是右边,以此让父亲继承儿子的右子树
                if (parent->_left == cur)
                {
                    parent->_left = cur->_right;
                }
                else
                {
                    parent->_right = cur->_right;
                }
            }
        }
        else if (cur->_right == nullptr) // 同理
        {
            if (cur == _root)
            {
                _root = cur->_left;
            }
            else
            {
                if (parent->_left == cur)
                {
                    parent->_left = cur->_left;
                }
                else
                {
                    parent->_right = cur->_left;
                }
            }
        }
        else
        {
            // 当左右子树均在时,找到左子树中最大数,让他当这块的根结点
            node *tmp = cur->_left, *p = cur;
            while (tmp->_right != nullptr)
            {
                p = tmp;
                tmp = tmp->_right;
            }
            // tmp指向此时左子树的最大值,即tmp无右子树
            // 现在要判断tmp在p的左边还是右边(因为有可能tmp的位置就是最大值了)
            if (p->_left == tmp)
            {
                p->_left = tmp->_left;
            }
            else
            {
                p->_right = tmp->_left;
            }
            std::swap(cur->_data, tmp->_data);
            cur = tmp;
        }
        delete cur;
        return true;
    }
    template <class K>
    bool BsTree<K>::_erase_R(typename BsTree<K>::node *&root, const K &key)
    {
        if (root == nullptr)
        {
            return false;
        }
        if (key > root->_data)
        {
            return _erase_R(root->_right, key); // return 是为了让该函数完成操作返回true/false时,可以快速出[递归出的一堆函数],因为只要返回值,当前函数就执行完成辽
        }
        else if (key < root->_data)
        {
            return _erase_R(root->_left, key);
        }
        else // 找到了
        {
            node *tmp = root; // 后续删除用
            if (root->_left == nullptr)
            {
                root = root->_right; // 此时root是它父结点左指针/右指针的别名,直接将其修改至root的非空结点即可
            }
            else if (root->_right == nullptr)
            {
                root = root->_left;
            }
            else
            { // 两边都不为空的话,就得找该结点的代替结点了(左子树中最大的结点)
                node *tr = root->_left;
                while (tr->_right != nullptr)
                {
                    tr = tr->_right;
                }
                std::swap(root->_data, tr->_data);
                return _erase_R(root->_left, tr->_data); // 此时已经互换值了,如果还在root找,万一rt就是root的left,换了就不符合平衡树了,也就根本找不到
                // 但既然tr可以代替root,那root自然也能代替tr,也就是说tr的左右子树是符合平衡树的,所以直接在root的左子树找就行
            }
            delete tmp; // 左子树中寻找时,肯定最终会找到,并且处理掉了那个结点的左子树,就会走到这里,删除,然后层层返回true
            tmp = nullptr;
            return true;
        }
    }
}

key_value版本实现

思路

和key版本的差不多,只不过为key值多了个与其关联的value值

比如,可以实现翻译功能(将中文与对应英文相互绑定)

实现基本就是复用key版本的代码,只需要修改一下模板参数和函数参数即可

代码

namespace key_value
{
    template <class K, class V>
    class BsTree_node
    {
    public:
        BsTree_node(const K &key, const V &value)
            : _key(key), _value(value), _left(nullptr), _right(nullptr) {}

        K _key;
        V _value;
        BsTree_node<K,V> *_left;
        BsTree_node<K,V> *_right;
    };

    template <class K, class V>
    class BsTree
    {
        typedef BsTree_node<K,V> node;

    public:
        // 构造+析构+拷贝
        BsTree()
            : _root(nullptr) {}
        ~BsTree()
        {
            _destroy(_root);
        }
        BsTree(const BsTree<K, V> &t)
        {
            _root = copy(t._root);
        }
        BsTree<K, V> &operator=(BsTree<K, V> t)
        {
            std::swap(_root, t._root);
            return *this;
        }
        // 遍历
        void inOrder()
        {
            _inOrder(_root);
            std::cout << std::endl;
        }
        // 各种功能
        bool insert(const K &key, const V &value);
        node *find(const K &key);
        bool erase(const K &key);

    private: // 所需的子函数
        void _destroy(node *root)
        {
            if (root == nullptr)
            {
                return;
            }
            _destroy(root->_left);
            _destroy(root->_right);
            delete root;
            root = nullptr;
        }
        void _inOrder(node *root) // 这里必须得传入参数,不然没法递归,但_root是私有成员,没法在类外调用,所以可以外部包装一层函数
        {
            if (root == nullptr)
            {
                return;
            }
            _inOrder(root->_left);
            std::cout << root->_key << ": " << root->_value << " ";
            _inOrder(root->_right);
        }
        node *copy(node *root)
        {
            node *cp = nullptr;
            if (root == nullptr)
            {
                return nullptr;
            }
            cp = new node(root->_key, root->_value);
            cp->_left = copy(root->_left);
            cp->_right = copy(root->_right);
            return cp;
        }

    private: // 成员
        node *_root;
    };

    // 函数实现
    template <class K, class V>
    bool BsTree<K, V>::insert(const K &key, const V &value)
    {
        if (_root == nullptr)
        {
            _root = new node(key, value);
        }
        else
        {
            node *cur = _root, *parent = cur;
            while (cur)
            {
                if (key > cur->_key)
                {
                    parent = cur;
                    cur = cur->_right;
                }
                else if (key < cur->_key)
                {
                    parent = cur;
                    cur = cur->_left;
                }
                else
                {
                    return false;
                }
            }
            if (key < parent->_key)
            {
                parent->_left = new node(key, value);
            }
            else
            {
                parent->_right = new node(key, value);
            }
        }
        return true;
    }

    //
    template <class K, class V>
    typename BsTree<K, V>::node *BsTree<K, V>::find(const K &key)
    {
        node *root = _root;
        while (root)
        {
            if (key > root->_key)
            {
                root = root->_right;
            }
            else if (key < root->_key)
            {
                root = root->_left;
            }
            else
            {
                return root;
            }
        }
        return nullptr;
    }

    //
    template <class K, class V>
    bool BsTree<K, V>::erase(const K &key)
    {
        node *cur = _root, *parent = cur;
        while (cur) // 找到删除的结点
        {
            if (key > cur->_data)
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (key < cur->_data)
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                break;
            }
        }
        if (cur == nullptr)
        {
            return false;
        }
        // 此时cur指向要被删除的结点
        if (cur->_left == nullptr) // cur左为空
        {
            if (cur == _root) // 防止cur此时是根结点
            {
                _root = cur->_right;
            }
            else
            {
                // 判断cur在parent的左边还是右边,以此让父亲继承儿子的右子树
                if (parent->_left == cur)
                {
                    parent->_left = cur->_right;
                }
                else
                {
                    parent->_right = cur->_right;
                }
            }
        }
        else if (cur->_right == nullptr) // 同理
        {
            if (cur == _root)
            {
                _root = cur->_left;
            }
            else
            {
                if (parent->_left == cur)
                {
                    parent->_left = cur->_left;
                }
                else
                {
                    parent->_right = cur->_left;
                }
            }
        }
        else
        {
            // 当左右子树均在时,找到左子树中最大数,让他当这块的根结点
            node *tmp = cur->_left, *p = cur;
            while (tmp->_right != nullptr)
            {
                p = tmp;
                tmp = tmp->_right;
            }
            // tmp指向此时左子树的最大值,即tmp无右子树
            // 现在要判断tmp在p的左边还是右边(因为有可能tmp的位置就是最大值了)
            if (p->_left == tmp)
            {
                p->_left = tmp->_left;
            }
            else
            {
                p->_right = tmp->_left;
            }
            std::swap(cur->_data, tmp->_data);
            cur = tmp;
        }
        delete cur;
        cur=nullptr;
        return true;
    }

}

应用 

void TestBSTree()
{
	BSTree<string, string> dict;
	dict.Insert("insert", "插入");
	dict.Insert("erase", "删除");
	dict.Insert("left", "左边");
	dict.Insert("string", "字符串");

	string str;
	while (cin>>str)
	{
		auto ret = dict.Find(str);
		if (ret)
		{
			cout << str << ":" << ret->_value << endl;
		}
		else
		{
			cout << "单词拼写错误" << endl;
		}
	}


	// 统计水果出现的次数
	string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };

	BSTree<string, int> countTree;
	for (auto str : strs)
	{
		auto ret = countTree.Find(str);
		if (ret == NULL)
		{
			countTree.Insert(str, 1);
		}
		else
		{
			ret->_value++;
		}
	}
	countTree.InOrder();
}

可以做出简易的翻译器: 

计数器:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值