【C++】二叉搜索树

二叉搜索树基本概念与结构

在这里插入图片描述
如图所示,这就是一颗二叉搜索树,其是由一个个结点通过指针链接起来的。
1.根节点的值大于左子树的任意结点的值,小于右子树任意结点的值
2.在树内不会出现相同的值
3.每个结点的左子树和右子树也分别为二叉搜索树

基本结构


template<class K>
struct BSTreeNode 
{
   BSTreeNode(K& key)    //结点的构造函数
       :_left(nullptr)
       ,_right(nullptr)
       ,_key(key)
     {}
  BSTreeNode* _left;  //左节点
  BSTreeNode* _right; //右节点
  K _key;             //值
};

template<class K>
class BSTree 
{
  typedef BSTreeNode<K> Node;
  public:
  BSTree()
    :_root(nullptr)
  {}
  private:
    Node* _root;     //根节点
};

搜索二叉树常用接口(循环实现)

Insert插入函数

bool Insert(K& key)
  {
  //当根节点为空,则key一定不存在树中
    if (_root == nullptr)
    {
      _root = new Node(key);
      return true;
    }
    Node* cur = _root;
    Node* parent = nullptr;  //记录父亲结点的位置
    while (cur)
    {
      //若key大于根节点值,到右子树查找
      if (key > cur->_key)
      {
        parent = cur;
        cur = cur->_right;
      }
       //若key小于根节点值,到左子树查找
      else if (key < cur->_key)
      {
        parent = cur;
        cur = cur->_left;
      }
      //若key等于根的值,则已经存在,返回假
      else 
        return false;
    }
    cur = new Node(key);
    //若key大于父亲结点key,则在父节点右边
    if (key > parent->_key)
      parent->_right = cur;
    else 
      parent->_left = cur;

  }

当想要插入的key在二叉树中已经存在时返回false,不存在则在正确位置插入_key = key 的结点

Find查找函数

bool Find(K& key)
  {
    Node* cur = _root;
    while (cur)
    {
    //若key大于根的值,则只可能在右子树
      if (key > cur->_key)
      {
        cur = cur->_right;
      }
    //key小于根的值,在左子树
      else if (key < cur->_key)
      {
        cur = cur ->_left;
      }
    //等于根的值,找到了
      else 
      {
        return true;
      }
    }
    //找不到返回false
    return false;
  }

InOrder中序遍历

void InOrder()
  {
    //新建一个栈,存储结点指针
    stack<Node*> st;
    Node* p = _root;
    do
    {
      //若有左节点则将左节点压入栈中
      while (p)
      {
        st.push(p);
        p = p->_left;
      }
      //取出最后一个左节点
      p = st.top();
      st.pop();
      //输出目前最左结点的值
      cout << p->_key << " ";
      //若这个节点有右子节点,需要进入这个结点的右子树用上述方法实现压栈
      if (p->_right)
        p = p->_right;
      else 
      //若没有右节点则将p制空,这样循环就不会进入第一层循环,直接可以取到栈中上一个结点(这个结点的父节点)
        p = nullptr;
    }while(p || !st.empty());
    cout << endl;
  }

由于二叉搜索树的性质,前序遍历可以将树中的key从小到大输出,可以通过这个接口验证代码的正确性。

Erase删除函数

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

bool Erase(const K& key)
  {
    Node* cur = _root;
    Node* parent = nullptr;
    while (cur)
    {
      //查找删除结点
      if (key > cur->_key)
      {
        parent = cur;
        cur = cur->_right;
      }
      else if (key < cur->_key)
      {
        parent = cur;
        cur = cur->_left;
      }
      else 
      {
        //找到需要删除的结点cur
        //在代码实现过程中,我们发现情况一和情况而的代码是接近相同的删除结点一边为空,父节点接收其另
        //一边结点指针,若另一边结点指针为空,则为情况一,若不为空,则为情况二
        if (cur->_left == nullptr)
        {
          //若要删除的是根节点,当其左子树或者右子树为空时需要特殊处理。因为此时parent为空
          //正常处理会产生错误访问
          if (parent == nullptr)
          {
            _root = _root->_right;
          }
          else 
          {
            //将删除结点的非空结点指针交给其父结点
            if (parent->_left == cur)
            {
              parent->_left = cur->_right;
            }
            else 
            {
              parent->_right = cur->_right;
            }
          }
          delete cur;
          cur = nullptr;
          return true;
        }
        else if (cur->_right == nullptr)
        {
          if (parent == nullptr)
          {
            _root = _root->_left;
          }
          else 
          {
            if (parent->_left == cur)
            {
              parent->_left = cur->_left;
            }
            else 
            {
              parent->_right = cur->_left;
            }
          }
          delete cur;
          cur = nullptr;
          return true;
        }
        else 
        {
        //情况三:
        //1.查找左子树最大结点
          Node* prev = cur;
          Node* left_max = cur->_left;
          while (left_max->_right)
          {
            prev = left_max;
            left_max = left_max->_right;
          }
          //替换左子树最大结点和替换结点的值
          cur->_key = left_max->_key;
          //若left_max有左节点,则交给其父prev
          if (prev->_right == left_max)
          {
            prev->_right = left_max->_left;
          }
          else 
          {
            prev->_left = left_max->_left;
          }
          delete left_max;
          left_max = nullptr;
          return true;
        }
      }
    }
    //没有找到指定key,返回假
    return false;
  }
  

搜索二叉树常用接口 (递归实现)

InOrder中序遍历(递归)

 void InOrderR() //无参数
  {
    _InOrder(_root); //调用子函数
  }

  void _InOrderR(Node* root) //有参数
  {
    if (root == nullptr)
    {
      return;
    }
    _InOrderR(root->_left);
    cout << root->_key << " ";
    _InOrderR(root->_right);
  }
  private:
    Node* _root;
};
//若只写一个函数的话,我们无法直接得到_root,需要添加接口取得_root.为了方便写一个子函数

由于二叉搜索树的性质,前序遍历可以将树中的key从小到大输出,可以通过这个接口验证代码的正确性。

Insert插入函数(递归)

bool InsertR(Node*& root, K& key)
  {
    if (root == nullptr)
    {
      root = new Node(key);
      return true;
    }
    if (key > root->_key)
    {
      return InsertR(root->_right, key);
    }
    else if (key < root->_key)
    {
      return InsertR(root->_left, key);
    }
    else 
    {
      return false;
    }
  }
   bool InsertR(K& key)
  {
     InsertR(_root, key);
  }


Find查找函数(递归)

bool Find(Node*& root, K& key)
   {
     if (key > root->_key)
     {
       return Find(root->_right, key);
      }
     else if (key < root->_key)
      {
        return Find(root->_left, key);
      }
      else 
      {
       return true;
      }
    }
    bool Find(K& key)
    {
      Find(_root, key);                                                                                                                                                               
    }

Erase删除函数(递归)

 bool EraseR(Node*& root, const K& key)
  {
  //若key大于根节点,则到右子树删,若小于则到左子树删
    if (key > root->_key)
    {
      return EraseR(root->_right, key);
    }
    else if (key < root->_key)
    {
      return EraseR(root->_left, key);
    }
    else 
    {
    //找到删除结点,使用del记录一下
    //root是上一个结点的左节点或者右节点指针的引用,我们可以直接改变root就可以改变父节点指针的指向
      Node* del = root;
      //情况1和2
      if (root->_left == nullptr)
      {
        root = root->_right;
      }
      else if (root->_right == nullptr)
      {
        root = root->_left;
      }
      //情况三
      else 
      {
      //找左子树最大结点
        Node* left_max = root->_left;
        Node* prev = root;
        while (left_max->_right)
        {
          prev = left_max;
          left_max = left_max->_right;
        }
        //方法一:
        //将左子树的值和删除结点的值进行交换
        swap(left_max->_key, root->_key);
        //此时值为key的结点肯定没有右节点,就满足情况一或二,因为跟的值肯定大于左子树,所以原来根
        //的左子树任然满足搜索二叉树,可以调用Erase输入左子树和key进行删除
        return EraseR(root->_left, key);
        //方法二:和非递归方法相同
        //if (prev->_left == left_max)
        //{
        //  prev->_left = left_max->_left;
        //}
        //else 
        //{
        //  prev->_right = left_max->_left;
        //}
        //root->_key = left_max->_key;
        //del =  left_max;
      }
      //删除指定结点并制空,返回真
      delete del;
      del = nullptr;
      return true;
    }
  }


为何递归频繁使用子函数

可以发现,在模拟实现递归接口时,我们频繁的使用了子函数,为什么要这样使用呢?

//我们可以看到InOrder函数在递归调用中,使用的root是不同的,所以函数有Node* root的参数
//当我们想要使用中序打印二叉树的时候,我们会发现我们无法直接得到根节点_root;
//为了得到根节点我们需要增加接口得到根节点,并用接口的返回值当作参数,调用的时候会非常奇怪
//为了增加代码的可读性,美观性,增加一个子函数,甚至还可以将子函数定义为私有

    void InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		InOrder(root->_left);
		cout << root->_key << " ";
		InOrder(root->_right);
	}
	//增加函数得到根节点的地址
	Node* get_root()
	{
		return this->_root;
	}
void TestBSTree()
{
	BSTree<int> t;
	int a[] = { 5,4,2,6,8,3,2,1 };
	for (auto e : a)
	{
		t.Insert(e);
	}
	t.InOrder(t.get_root()); //调用十分的别扭
}

函数重载的价值也可以在这里体现,两个函数可以同名,但是最好在子函数开头加上_(下划线)进行标识,还可以将子函数定义为private中,更好的体现了封装

搜索树的价值

1.搜索
key搜索模型(判断在不在,有没有)

判断在不在,可以应用在学校门禁系统中。校园卡储存了学生的学号等信息。我们可以将每一栋宿舍的学生的学号插入一个二叉搜索树。每次进门刷卡时校园卡读取学号,在二叉搜索树中进行查找。找得到返回真开门,找不到返回假不开门

key/value搜索模型(通过一个值查找另一个值)
通过一个值来找到另外的值,例如高铁站我们通过刷身份证就可以进站。这是因为我们的身份证上有我们的身份信息,例如身份证号。高铁站将我们的身份证号和票的信息关联起来。这样就可以通过刷卡时读取到的身份证号(key)在搜索树中找到我们的val(票信息),一个人可能有多张票,所以val还可以设计成vector。然后通过比对val和闸机上的比对信息,就可以判断是否开门
2.排序+去重

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白在进击

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值