二叉搜索树

二叉搜索树的结构

二叉搜索树其实和二叉树差不多,都是一颗树,但是它有点排序的作用,因为比根节点大的在根的右边,比根节点小的在根节点的左边,以此类推,这样下来只要走一个高度次就能确认一个数在不在里面,不过这是最好的情况,因为如果是有序或者一直来大于或者小于根的值,那么这棵树虽然还是搜索树,但是其实已经失衡了,这样效率就会变低,所以它还是带点缺陷的。

这就是它的基本结构,需要注意的是这里的节点暂时是不允许重复且不能插入的,后面会有multi版本支持重复,但是还是不允许插入,因为它害怕结构被破坏。

二叉搜索树的实现

  

为了方便写 我们先typedef一下咱的树节点

插入insert:

	bool insert(const K& key)
		{
			if (_root == nullptr)//二叉树老样子
			{
				_root = new node(key);//不过在这里如果为空需要new一个根节点出来
				return true;
			}
			node* parent = nullptr;//按照后面的逻辑 我们的cur会走到虚空里去 所以得记录一下它的父
			node* cur = _root;//这两行就是前后指针 方便cur走到虚空后 父还能连接到cur
			while (cur)//递归和循环 当然是选循环了 规避一下栈溢出
			{

				if (cur->_key < key)//按照逻辑 大走右
				{
					parent = cur;//前后指针记录一下
					cur = cur->_right;
				}
				else if (cur->_key > key)//小走左
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;//走到这说明因为某些原因失败了(比如重复的数) 直接返回flase就好
				}
			}

			node* newnode = new node(key);//上面走完说明父和cur都找到指定位置了 那么先new一个key值的节点出来
			if (parent->_key < key)//然后判断大于还是小于父 直接插入即可
			{
				parent->_right = newnode;

			}
			else
			{
				parent->_left = newnode;
			}
			return true;//走到这说明插入成功 返回true
		}

找find:

	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;//不大不小不重复就是找到了 返回true
				}

			}
			return false;//走到这说明找不到
		}

删除erase:

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
                {
                    //这里就需要区分情况 
                    //一共有四种情况 左为空 右为空 左右都为空 左右都不为空
                    //前面三个我们其实可以归为两种 左为空 右为空
                    if (cur->_left == nullptr)//在左为空的大前提下
                    {
                        if (cur == _root)//如果我本身就是根 那删除就太简单了 我的右升级为根 
                        {
                            _root = cur->_right;
                        }
                        else//不然我不是根 那就老老实实找替代品
                        {
                            if (parent->_left == cur)//如果上面更新完的父的左等于cur 那说明右为空
                            {
                                //把cur的右连接到原本cur的位置
                                parent->_left = cur->_right;
                            }
                            else//不然就是左为空
                            {
                                //把cur的右连接到cur的位置
                                parent->_right = cur->_right;
                            }
                        }
                        delete cur;//连接完cur就没用了 正常删除即可
                    }

                    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;
                            }
                        }
                        delete cur;
                    }
                    //左右有一个为空的还算简单 接下来左右都不为空的就有点难了
                    else
                    {
                        //换一种方式记录父和cur 因为原本的父不用动
                        node* rparent = cur;
                        node* replace = cur->_right;//替代品
                        while (replace->_left)
                        {
                            rparent = replace;
                            replace = replace->_left;
                        }
                        //走到这我们可以确认是两边都有节点的情况
                        //又根据搜索树的特性我们可以知道 左的最右和右的最左其实就是很好的替代品
                        //用它们替代被删除的位置很完美
                        //所以我们根据这俩特性任选一边当替代品即可
                        cur->_key = replace->_key;//直接把要删除的位置的数据改成替代品的数据
                        if (rparent->_left == replace)
                        {
                            rparent->_left = replace->_right;//上面走完rplace已经属于是孤儿节点了
                            //所以其实就是把replace的位置先置为空
                        }
                        else
                        {
                            //同理
                            rparent->_right = replace->_right;
                        }
                        //然后删除replace就好
                        delete replace;

                    }
                    //能走到这就说明成功了
                    return true;
                }
            }
            return false;//while走完没返回true 我管你啥问题我都先返回false再说

        }

其实双节点的删除就是这么个过程

单节点就更简单了

    二叉搜索树除了删除复杂一点,其实大部分还是二叉树的正常操作,多出来的特性只是提升了它一点点功能而已。关键的提升在于map和set对它的提升

使用场景

对于二叉搜索树的使用场景其实有两个,一个就是上面我们实现的key版本,但是它也多少有点缺陷,因为不能修改,所以衍生了一个 key + key_value的版本出来。

它既照顾到了不能修改的特性又实现了修改,它的key还是不能修改,但是它有一个key_vlaue用来存值。

比如说停车场,进了车先记录车牌,也就是key,车牌不能修改,同时key_value记录进场时间,等要走的时候用现在的时间减去key_value记录的时间,但是如果我和停车场保安是亲戚,我就可以叫他改一下我的入场时间,直接免费停车。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值