二叉搜索树全面解析

本文详细介绍了二叉搜索树的插入、删除(包括复杂情况下的替换策略)、查找操作,并展示了中序遍历的实现。通过实例验证了代码的正确性,适用于初学者理解二叉搜索树的数据结构和算法应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


二叉搜索树介绍

所谓二叉搜索树首先它是一棵二叉树,其次满足以下的规则:
每一个结点的左子树中的任意结点比这个结点小,右子树的任意结点比这个结点大。举个栗子。
1
5的左子树全比5小,右子树全比5大,3的左子树全比3小,右子树全比3大,所有结点都满足这个规则。


一、insert

搜索二叉树的插入非常easy,就按照规则,比当前结点大则往右边走,比当前结点小则往左走。

//二叉搜索树框架
template<class T>
struct BSTreeNode
{
   typedef BSTreeNode<T> node;
   node* _left;
   node* _right;
   T  _key;

  BSTreeNode(const T& key)
        :_left(nullptr)
        ,_right(nullptr)
        ,_key(key)
        {}
};
template<class T>
class BSTree
{
   typedef BSTreeNode<T> node;
 public:
      //成员函数
 private:
    node* _root = nullptr;
};
bool insert(const T& key)
{
    if(_root == nullptr) //空树
    {
       _root = new node(key);
       return true;
    }
    node* parent = nullptr;//记录cur的父节点,为了连接cur
    node* cur = _root; //cur找到插入的合适位置
   while(cur)
   {
      if(key < cur->_key)//往左边走
      {
         parent = cur;
         cur = cur->_left;
      }
      else if(key > cur->_key)//往右边走
      {
         parent = cur;
         cur = cur->_right;
      }
      else //相等不插入
      {
         return false;
      }
   }
   //找到了位置,判断是在parent左边还是右边
   cur = new node(key);
   if(key < parent->_key) //在parent左边
   {
       parent->_left = cur;
   }
   else //在cur右边
   {
      parent->_right = cur;
   }
   return true;
}

二、erase

erase是我们的重头戏,这个玩意不好写。
我们先分类讨论一下,假设要删除的结点是cur,有下面这几种情况。

  1. cur的右边为空。
  2. cur的左边为空且右边不为空。
  3. cur的左右子树都不为空。

我们逐个情况进行分析。情况1,
123
情况2,
22
情况3有点意思,如果一个结点左右子树都非空,那怎么搞?我直接干掉它吗?这不太好,所以我们想了个办法,用另外一个结点去替代它,然后再干掉那个替代结点。首先替代结点应该是容易删除,不然这种方法毫无意义,其次,**替代完成后,仍然满足二叉搜索树的所有性质。**综合以上考虑,我们可以使用cur的左子树的最右结点 or 右子树的最左结点。

这里我选择右子树的最左结点来完成这个operation。
67
接下来我们来实现代码。

void erase(const T& key)
{
   //首先找到要删除的结点
   node* parent = nullptr;
   node* cur = _root;
   while(cur)
   {
     if(key < cur->_key) 
     {
        parent = cur;
        cur = cur->_left;
     }
     else if(key > cur->_key)
      {
        parent = cur;
        cur = cur->_right;
     }
     else 
      {
        //找到了
        //1. cur的右边为空。
        //2. cur的左边为空且右边不为空。
        // 3. cur的左右子树都不为空。
        //但是还有一点要注意,parent可能为空,防止根结点就是要删除的结点。
        if(cur->_right == nullptr) 
         {
           if(parent == nullptr)//父节点为空
         {
               _root = cur->_left;
           }
           else
           {
              if(cur == parent->_left)//cur是父节点的左边
              {
                 parent->_left = cur->_left;
              }
              else //cur是父节点的右边
              {
                 parent->_right = cur->_left;
              }
           }
           delete cur;
        }
        else if(cur->_left == nullptr)
        {
           if(parent == nullptr) //父节点为空
           {
              _root = cur->_right;
           }
           else
           {
              if(cur == parent->_left)//cur是父节点的左边
              {
                 parent->_left = cur->_right;
              }
              else //cur是父节点的右边
              {
                parent->_right = cur->_right;
              }
           }
           delete cur;
        }
        else
        {
           //rLMin是右子树的最小结点
           //rLMinParent是rLMin的父节点
            node* rLMin = cur->_right;
            node* rLMinParent = cur; //这里不能给nullptr,
                                       //否则下面解引用可能报错
            while(rLMin->_left) //找到rLMin
            {
               rLMinParent = rLMin;
               rLMin = rLMin->_left;
            }
            cur->_key = rLMin->_key;
            if(rLMin == rLMinParent->_right)//rLMin就是cur的右节点
            {
               rLMinParent->_right = rLMin->_right;
            }
            else
            {
               rLMinParent->_left = rLMin->_right;
            }
            delete rLMin;
        }
        
         return;
         }
   }
   return ;
}

三,find

find就更easy了,找到返回true,没找到返回false。

bool find(const T& key)
{
   node* cur = _root;
   while(cur)
   {
      if(key < cur->_key){
         cur = cur->_left;
      }
      else if(key > cur->_key){
          cur = cur->_right;
      }
      else{
         return true;
      }
   }
   return false;
}

四,中序遍历

二叉搜索树除了便于查找,它的中序遍历还是有序的,中序遍历代码如下:

void _InOrder(node* root) //这是InOrder的子函数
{
    if(root == nullptr)
       return;
      _InOrder(root->_left);
      cout << root->_key << " " ;
      _InOrder(root->_right);
}
void InOrder()
{
   _InOrder(_root);
   cout << endl;
}

中序遍历的递归为什么要这样写呢?因为我需要参数root,但在类外部无法访问根节点,所以写一个子函数去递归。

五,验证代码

验证的代码:

void test_bstree()
{
	BSTree<int> bst;
	int a[] = { 5,3,7,1,4,6,8,0,2,9 };
	cout << "insert的检验结果 :" << endl;
	for (auto e : a)
	{
		bst.insert(e);
	}
	cout << "InOrder的检验结果 :" << endl;
	bst.InOrder();

	cout << "find的检验结果 :" << endl;
	for (auto e : a)
	{
		cout << bst.find(e) << " ";
	}
	cout << "erase的检验结果 :" << endl;
	for (auto e : a)
	{
		bst.erase(e);
		bst.InOrder();
	}
}

在VS上的result:
result
(全文完)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值