【C++从0到王者】第二十七站:搜索二叉树

简介: 【C++从0到王者】第二十七站:搜索二叉树

前言

在前面我们其实已经分析过二叉树了,所谓二叉树就是度为2的树。而二叉树中,又有一种特殊的树称之为二叉搜索树

一、二叉搜索树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

为什么我们将这样的树称作二叉搜索树呢?因为这样的树天生就十分适合搜索。是想一下,使用上图中最右边的树,如果我们想找到7的话,首先7比6大,所以一定在右子树,而7小于9,所以一定在左子树。结果发现就找到了。从而我们可以发现,这种树的效率天生就很高。

而且这棵树最多找高度次。也就是说最多找N次(注意不是logN次,因为我们可能碰到下图中右边的树),下图中的两棵树都是二叉搜素树。也就是说时间复杂度为O(N)

普通的二叉搜索树的时间复杂度为O(N),但是我们可以对其进行一些特殊变换,使之成为AVL树,红黑树。这样时间复杂度就可以降为了O(logN)了。

这棵树还有一个名字是二叉排序树,这是因为当我们对这棵树进行中序遍历的时候,结果是升序的。

二、二叉搜索树的实现

1.二叉树的结点定义

对于二叉树的结点,我们类似于list的结点定义一样,使用一个struct类。在这个类中,我们可以有默认构造函数,这个默认构造函数的缺省值为K()。

template<class K>
struct BSTreeNode
{
  BSTreeNode(const K& val = K())
    :_key(val)
    ,_left(nullptr)
    ,_right(nullptr)
  {}
  K _key;
  BSTreeNode<K>* _left;
  BSTreeNode<K>* _right;
};

2.二叉搜索树的结构

为了方便我们后序的结点的定义,我们可以先将结点给typedef一下。然后我们的成员变量其实也就这一个根节点。只要有根节点就可以构建出二叉搜索树了。

private:
  typedef BSTreeNode<K> Node;
  Node* _root;

3.二叉搜索树的构造函数

public:
  BSTree()
    :_root(nullptr)
  {}

如上代码所示,对于构造函数其实是比较简答的,我们只需要将二叉树的根结点初始化为空即可

4.二叉搜索树的插入(非递归)

对于插入接口,我们需要考虑的情况有如下几种:

  1. 二叉树为空的时候,直接new一个结点插入上去即可
  2. 如果二叉树不为空,那么我们就需要进行比较
  • 如果我们想要插入的值大于当前结点的值,那么我们就将当前结点迭代到右孩子
  • 如果我们想要插入的值小于当前结点的值,那么我们将当前结点迭代到左孩子
  • 如果恰好等于当前结点的值,那么我们可以认为插入失败了,因为此时已经不满足二叉搜索树的定义了。
  • 最终我们会发现如此迭代下去,最终的当前结点一定会为空指针,这并非我们所期望的。我们肯定是还需要它的父节点的,这样才能让我目标值插入进去。所以我们还需要一个值来记录当前所迭代位置的父节点。即我们下面代码的parent
  • 有了parent以后,我们现在就是new一个新的结点,然后直接parent链接起来。不过此时又分为两种情形,是左孩子还是右孩子,其实并不好确定。于是我们可以使用parent当前的值与目标值进行比对从而判断出是插入左孩子还是右孩子。
public:
  bool Insert(const K& key)
  {
    if (_root == nullptr)
    {
      _root = new Node(key);
      return true;
    }
    else
    {
      Node* parent = _root;
      Node* cur = _root;
      while (cur != nullptr)
      {
        if (cur->_key == key)
        {
          return false;
        }
        else if (cur->_key > key)
        {
          parent = cur;
          cur = cur->_left;
        }
        else
        {
          parent = cur;
          cur = cur->_right;
        }
      }
      cur = new Node(key);
      if (parent->_key > key)
      {
        parent->_left = cur;
      }
      else if (parent->_key < key)
      {
        parent->_right = cur;
      }
      return true;
    }
  }

5.二叉搜索树的中序遍历(排序)

插入了以后,我们可以使用中序遍历对其进行输出,由于中序遍历就是排序后的情形,所以我们可以很方便的看到我们当前二叉树的结果。

对于排序,我们最好写一个子函数来实现,因为没有子函数的话,参数不好进行控制,会使得代码十分繁琐。

如下代码所示,对于中序遍历我们其实还是比较容易看懂的。无非就是先左子树,然后根,然后右子树即可。我们最好将子函数放在私有的部分中,因为在类外我们并不需要直接使用这个函数

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

6.二叉搜索树的查找(非递归)

对于查找的逻辑,是非常简单的,它与插入也是比较相似的,当我们当前结点的值小于目标的时候,迭代到右子树,当大于目标的时候,迭代到左子树。当相等的时候返回true即可。但若当cur都为nullptr了的时候还没有找到,那就是没有找到,返回false即可

public:
  bool Find(const K& key)
  {
    Node* cur = _root;
    while (cur)
    {
      if (cur->_key == key)
      {
        return true;
      }
      else if (cur->_key > key)
      {
        cur = cur->_left;
      }
      else if (cur->_key < key)
      {
        cur = cur->_right;
      }
    }
    return false;
  }

7.二叉搜索树的删除(非递归)

对于二叉搜索树的删除操作,其实是比较难的。这里出现了至少十种以上的情形。

  1. 找目标结点。这里的找目标结点,我们采用与插入法类似的操作,有一个当前结点和一个父结点。一开始自然是cur为根节点,parent为nullptr。然后我们进行迭代, 当cur不为空的时候,如果当前cur里面的值大于目标值,则parent迭代为cur,cur迭代为左孩子。如果cur里面的值小于目标值的话,则parent迭代为cur,cur迭代为右孩子。如果相等的情况下,由于比较复杂,我们在后面在进行讨论。如果迭代下去。只要没有出现相等的情形,迟早会离开循环,离开循环了,那么直接返回false即可
  2. 如果中间遇到相等的情况,也就是说,我们已经找到了目前要删除的值,这个值就是cur所指向的结点。这个时候,我们需要进一步将其进行分析。
  • 这里存在的主要问题就是cur与parent以及cur的孩子之间的关系比较复杂。
  • 首先cur的孩子就分为四种情况,分别是没有孩子、只有左孩子、只有右孩子、两个孩子都有。由于我们要删除cur结点,所以孩子的情况的不同,我们所需要的处理方式就不一样。
  • 如果没有孩子的话,那么其实我们只需要直接删除该结点,然后将parent所指向cur的位置给置空即可。如下图所示:即当我们想要在下面这棵树中删除13的时候的情形

  • 如果cur只有一个左孩子,也就是说右孩子为空。那么我们可以,使用托孤的方式,即将cur的左孩子直接与parent连接起来,然后在直接删除cur即可

  • 如果cur只有一个右孩子,也就是说,左孩子为空,那么我们可以和上面一样采用类似的手段,托孤,将 cur的右孩子直接与parent进行连接,然后删除cur

  • 如果两个孩子都存在的话,那么此时情形比较复杂,我们采用替代法。替代法的基本思路是,将要删除的该结点视为一棵子树,然后找到这棵树的左子树的最大值,或者右子树的最小值。然后我们与这两个中的任何一个进行替换即可,然后删除原来被替换的结点。如下图所示、

  • 其实关于以上的cur与其孩子的四种情形,可以简化为三种情况:即将没有孩子归为只有一个左孩子或者只有一个右孩子的情形,因为我们可以将他们的孩子视为一个nullptr,如果这样思考的话,那么前三种方案都是使用托孤的思路。唯独第四种,我们需要使用替代法进行解决
  • 除了cur孩子之间的问题以外,还有parent和cur之间的关系也是存在一些情况的。
  • cur为parent的左孩子或右孩子,这种情形是比较简单的,也是前面图中的情形。我们只需要与cur与它的孩子的情况一关联即可。其实就是前面cur与其孩子的变化。
  • parent不存在,即cur就是根节点,这种情况我们需要进行特殊处理,因为此时已经不存在父节点了
    ① 当cur的左孩子或者右孩子任意一个为空的时候,此时我们采用托孤的处理方案,不过此时的托孤需要一些变化,因为cur其实就是_root,所以我们直接让_root赋值为cur->_left或者cur->_right即可。

    ②当cur的左孩子和右孩子都存在的时候,我们还是使用替代法
    如下是我们正常时候的替代,变化还不是很大。

    但是当恰好leftMax就是cur的左侧结点的时候,这时候就出现意外了,因为我们是需要进行连接的,所以需要知道leftMax的父节点是谁才可以方便连接,上图是因为leftMax肯定是它的右孩子,而此时变成了左孩子,所以需要特殊处理。(这里的特殊处理在前面的每一次使用替代法中都会出现这种情况)

根据以上的分析,我们已经可以写出以下的删除代码了:

由于这里存在三代人之间的关系,即parent与cur之间的关系可分为三种情况,cur与其孩子的关系可分为三种情况,这里可以不严格的要求先考哪一类情况。我们两种代码都给出

以下是先考虑parent与cur这两代人之间的关系所写的代码

public:
  bool Erase1(const K& key)
  {
    Node* cur = _root;
    Node* parent = nullptr;
    while (cur)
    {
      if (cur->_key > key)
      {
        parent = cur;
        cur = cur->_left;
      }
      else if (cur->_key < key)
      {
        parent = cur;
        cur = cur->_right;
      }
      else
      {
        if (parent == nullptr)
        {
          if (cur->_left == nullptr)
          {
            _root = cur->_right;
            delete cur;
            return true;
          }
          else if (cur->_right == nullptr)
          {
            _root = cur->_left;
            delete cur;
            return true;
          }
          else
          {
            Node* leftMaxParent = cur;
            Node* leftMax = cur->_left;
            if (leftMax->_right == nullptr)
            {
              leftMax->_right = cur->_right;
              delete cur;
              _root = leftMax;
              return true;
            }
            while (leftMax->_right)
            {
              leftMaxParent = leftMax;
              leftMax = leftMax->_right;
            }
            std::swap(leftMax->_key, cur->_key);
            leftMaxParent->_right = leftMax->_left;
            delete leftMax;
            leftMax = nullptr;
            return true;
          }
        }
        if (parent->_left == cur)
        {
          if (cur->_left == nullptr)
          {
            parent->_left = cur->_right;
            delete cur;
            return true;
          }
          else if (cur->_right == nullptr)
          {
            parent->_left = cur->_left;
            delete cur;
            return true;
          }
          else 
          {
            Node* leftMaxParent = cur;
            Node* leftMax = cur->_left;
            if (leftMax->_right == nullptr)
            {
              leftMax->_right = cur->_right;
              delete cur;
              parent->_left = leftMax;
              return true;
            }
            while (leftMax->_right)
            {
              leftMaxParent = leftMax;
              leftMax = leftMax->_right;
            }
            std::swap(leftMax->_key, cur->_key);
            leftMaxParent->_right = leftMax->_left;
            delete leftMax;
            leftMax = nullptr;
            return true;
          }
        }
        else
        {
          if (cur->_left == nullptr)
          {
            parent->_right = cur->_right;
            delete cur;
            return true;
          }
          else if (cur->_right == nullptr)
          {
            parent->_right = cur->_left;
            delete cur;
            return true;
          }
          else
          {
            Node* leftMaxParent = cur;
            Node* leftMax = cur->_left;
            if (leftMax->_right == nullptr)
            {
              leftMax->_right = cur->_right;
              delete cur;
              parent->_right = leftMax;
              return true;
            }
            while (leftMax->_right)
            {
              leftMaxParent = leftMax;
              leftMax = leftMax->_right;
            }
            std::swap(leftMax->_key, cur->_key);
            leftMaxParent->_right = leftMax->_left;
            delete leftMax;
            leftMax = nullptr;
            return true;
          }
        }
      }
    }
    return false;
  }

以下是根据先考虑cur与其孩子之间的关系所写的代码:

public:
  bool Erase2(const K& key)
  {
    Node* cur = _root;
    Node* parent = nullptr;
    while (cur)
    {
      if (cur->_key > key)
      {
        parent = cur;
        cur = cur->_left;
      }
      else if (cur->_key < key)
      {
        parent = cur;
        cur = cur->_right;
      }
      else
      {
        if (cur->_left == nullptr)
        {
          if (parent == nullptr)
          {
            _root = cur->_right;
          }
          else if (parent->_left == cur)
          {
            parent->_left = cur->_right;
          }
          else if (parent->_right == cur)
          {
            parent->_right = cur->_right;
          }
        }
        else if (cur->_right == nullptr)
        {
          if (parent == nullptr)
          {
            _root = cur->_left;
          }
          else if (parent->_left == cur)
          {
            parent->_left = cur->_left;
          }
          else if (parent->_right = cur)
          {
            parent->_right = cur->_left;
          }
        }
        else
        {
          Node* leftMax = cur->_left;
          Node* leftMaxParent = cur;
          while (leftMax->_right)
          {
            leftMaxParent = leftMax;
            leftMax = leftMax->_right;
          }
          std::swap(cur->_key, leftMax->_key);
          if (leftMaxParent->_left == leftMax)
          {
            leftMaxParent->_left = leftMax->_left;
          }
          else
          {
            leftMaxParent->_right = leftMax->_left;
          }
          cur = leftMax;
        }
        delete cur;
        return true;
      }
    }
  }

可见,虽然无论先写哪一种情形,其实都可以完成这个接口,但是代码量有显著的差异。这里推荐先考虑cur和其后代之间的关系。因为替代法这个出现于cur与其后代之间的关系之中。

8.二叉搜索树的查找(递归)

二叉树其实本身还是比较适合递归的。这里的思路也很简单, 不过我们为了可以控制参数,我们必须要写一个子函数去解决,当根节点为空的时候,直接返回false即可,就是没找到,当当前的结点小于要查找的结点,那么去右子树找,当大于时候,去左子树找即可。

public:
  bool FindR(const K& key)
  {
    return _FindR(_root, key);
  }
private:
  bool _FindR(Node* root, const K& key)
  {
    if (root == nullptr)
    {
      return false;
    }
    if (root->_key == key)
    {
      return true;
    }
    else if (root->_key > key)
    {
      return _FindR(root->_left, key);
    }
    else
    {
      return  _FindR(root->_right, key);
    }
  }

9.二叉搜索树的插入(递归)

这里的递归插入也是比较简单的,我们仍然使用子函数调用,当小于待插入的值的时候则去右子树插入,当大于待插入的值的时候,去左子树插入,但是如果遇到相等了,那么就只能插入失败了。最终我们一定会遇到一个空树,我们可以直接new一个结点来,不过这里的问题是如何连接?其实我们可以使用引用,使用引用的话,此时的root本身就早已跟父节点连接到一块了,所以我们直接给这个结点进行赋值,即可解决问题。

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

10.二叉搜索树的删除(递归)

如下是递归式删除的操作。在这里我们还是使用子函数调用方便控制参数。这里最好使用引用,因为引用的话永远都是这一棵树。比较容易操作,否则就得传入二级指针就比较麻烦了。

如果root为空,就代表这棵树里面根本就没有key,自然直接返回nullptr即可。

如果当前结点的值小于key,那么去右子树删除,如果大于,去左子树删除。

当等于的时候,我们可以这样操作:先将待删除的结点给记录下来,如果该节点左孩子为空,或者右孩子为空的话,我们可以直接覆盖的方式去迭代。这步操作相对而言是比较妙的。

那么我们不妨思考一下:前面的非递归形式,能否使用这种方式来进行迭代呢?其实是不可以的,这里能迭代是因为引用,永远都是那个空间,且由于在不同的栈帧原因导致了引用的指向被巧妙的改变了。而前者他们并没有使用引用,都是使用局部变量来进行迭代的,因为C++中的引用是不可以改变指向的。所以自然不可以。

如下图所示,非递归中,迭代的本质,仅仅只是局部变量的不断更替,不会直接影响root,这里局部变量的修改仅仅只是间接修改堆区已有数据的连接关系。如果直接赋值的话,只是修改了栈区的局部变量的数据,并未修改实际关系。

如果cur的两个孩子都存在的话,我们还是使用替代法,设置一个局部变量leftMax,让其指向cur->_left,然后不断迭代他,找到这颗树的左子树最大结点。然后交换结点,最后我们知道要删除的结点一定在左子树,所以我们使用递归在左子树中找到要删除的结点即可。

注意这里千万不要将leftMax给传入了。这样做看似可以,但是leftMax是一个局部变量,等到离开本层的递归的时候,leftMax被断了,整棵树的逻辑关系全部混乱了。

public: 
  bool EraseR(const K& key)
  {
    return _EraseR(_root, key);
  }
private:
  bool _EraseR(Node*& root, const K& key)
  {
    if (root == nullptr)
    {
      return false;
    }
    if (root->_key < key)
    {
      return _EraseR(root->_right, key);
    }
    else if (root->_key > key)
    {
      return _EraseR(root->_left, key);
    }
    else
    {
      Node* del = root;
      if (root->_left == nullptr)
      {
        root = root->_right;
      }
      else if (root->_right == nullptr)
      {
        root = root->_left;
      }
      else
      {
        Node* leftMax = root->_left;
        while (leftMax->_right)
        {
          leftMax = leftMax->_right;
        }
        std::swap(leftMax->_key, root->_key);
        return _EraseR(root->_left, key);
      }
      delete del;
      return true;
    }
  }

11.二叉搜索树的销毁

销毁操作比较简答, 直接按照后序的方式进行销毁即可。最好传引用,因为可以置空root指针

public:
  ~BSTree()
  {
    _Destory(_root);
  }
private:
  void _Destory(Node*& root)
  {
    if (root == nullptr)
    {
      return;
    }
    _Destory(root->_left);
    _Destory(root->_right);
    delete root;
    root == nullptr;
  }

12.拷贝构造函数

拷贝构造函数,我们需要实现一个深拷贝,这里我们使用先序的方式进行构造。

public:
  BSTree(const BSTree<K>& t)
  {
    _root = Copy(t._root);
  }
private:
  Node* Copy(Node* root)
  {
    if (root == nullptr)
    {
      return nullptr;
    }
    Node* Copyroot = new Node(root->_key);
    Copyroot->_left = Copy(root->_left);
    Copyroot->_right = Copy(root->_right);
    return Copyroot;
  }

13.赋值运算符重载

赋值运算符重载,我们可以使用现代写法,这样做是最方便的

public:
  BSTree<K>& operator=(BSTree<K> t)
  {
    std::swap(_root, t._root);
    return *this;
  }

三、二叉搜索树的实现完整代码

#pragma once
template<class K>
struct BSTreeNode
{
  BSTreeNode(const K& val = K())
    :_key(val)
    ,_left(nullptr)
    ,_right(nullptr)
  {}
  K _key;
  BSTreeNode<K>* _left;
  BSTreeNode<K>* _right;
};
template<class K>
class BSTree
{
  typedef BSTreeNode<K> Node;
public:
  BSTree()
    :_root(nullptr)
  {}
  BSTree(const BSTree<K>& t)
  {
    _root = Copy(t._root);
  }
  ~BSTree()
  {
    _Destory(_root);
  }
  BSTree<K>& operator=(BSTree<K> t)
  {
    std::swap(_root, t._root);
    return *this;
  }
  bool Insert(const K& key)
  {
    if (_root == nullptr)
    {
      _root = new Node(key);
      return true;
    }
    else
    {
      Node* parent = _root;
      Node* cur = _root;
      while (cur != nullptr)
      {
        if (cur->_key == key)
        {
          return false;
        }
        else if (cur->_key > key)
        {
          parent = cur;
          cur = cur->_left;
        }
        else
        {
          parent = cur;
          cur = cur->_right;
        }
      }
      cur = new Node(key);
      if (parent->_key > key)
      {
        parent->_left = cur;
      }
      else if (parent->_key < key)
      {
        parent->_right = cur;
      }
      return true;
    }
  }
  void InOrder()
  {
    _InOrder(_root);
  }
  bool Find(const K& key)
  {
    Node* cur = _root;
    while (cur)
    {
      if (cur->_key == key)
      {
        return true;
      }
      else if (cur->_key > key)
      {
        cur = cur->_left;
      }
      else if (cur->_key < key)
      {
        cur = cur->_right;
      }
    }
    return false;
  }
  bool Erase1(const K& key)
  {
    Node* cur = _root;
    Node* parent = nullptr;
    while (cur)
    {
      if (cur->_key > key)
      {
        parent = cur;
        cur = cur->_left;
      }
      else if (cur->_key < key)
      {
        parent = cur;
        cur = cur->_right;
      }
      else
      {
        if (parent == nullptr)
        {
          if (cur->_left == nullptr)
          {
            _root = cur->_right;
            delete cur;
            return true;
          }
          else if (cur->_right == nullptr)
          {
            _root = cur->_left;
            delete cur;
            return true;
          }
          else
          {
            Node* leftMaxParent = cur;
            Node* leftMax = cur->_left;
            if (leftMax->_right == nullptr)
            {
              leftMax->_right = cur->_right;
              delete cur;
              _root = leftMax;
              return true;
            }
            while (leftMax->_right)
            {
              leftMaxParent = leftMax;
              leftMax = leftMax->_right;
            }
            std::swap(leftMax->_key, cur->_key);
            leftMaxParent->_right = leftMax->_left;
            delete leftMax;
            leftMax = nullptr;
            return true;
          }
        }
        if (parent->_left == cur)
        {
          if (cur->_left == nullptr)
          {
            parent->_left = cur->_right;
            delete cur;
            return true;
          }
          else if (cur->_right == nullptr)
          {
            parent->_left = cur->_left;
            delete cur;
            return true;
          }
          else 
          {
            Node* leftMaxParent = cur;
            Node* leftMax = cur->_left;
            if (leftMax->_right == nullptr)
            {
              leftMax->_right = cur->_right;
              delete cur;
              parent->_left = leftMax;
              return true;
            }
            while (leftMax->_right)
            {
              leftMaxParent = leftMax;
              leftMax = leftMax->_right;
            }
            std::swap(leftMax->_key, cur->_key);
            leftMaxParent->_right = leftMax->_left;
            delete leftMax;
            leftMax = nullptr;
            return true;
          }
        }
        else
        {
          if (cur->_left == nullptr)
          {
            parent->_right = cur->_right;
            delete cur;
            return true;
          }
          else if (cur->_right == nullptr)
          {
            parent->_right = cur->_left;
            delete cur;
            return true;
          }
          else
          {
            Node* leftMaxParent = cur;
            Node* leftMax = cur->_left;
            if (leftMax->_right == nullptr)
            {
              leftMax->_right = cur->_right;
              delete cur;
              parent->_right = leftMax;
              return true;
            }
            while (leftMax->_right)
            {
              leftMaxParent = leftMax;
              leftMax = leftMax->_right;
            }
            std::swap(leftMax->_key, cur->_key);
            leftMaxParent->_right = leftMax->_left;
            delete leftMax;
            leftMax = nullptr;
            return true;
          }
        }
      }
    }
    return false;
  }
  bool Erase2(const K& key)
  {
    Node* cur = _root;
    Node* parent = nullptr;
    while (cur)
    {
      if (cur->_key > key)
      {
        parent = cur;
        cur = cur->_left;
      }
      else if (cur->_key < key)
      {
        parent = cur;
        cur = cur->_right;
      }
      else
      {
        if (cur->_left == nullptr)
        {
          if (parent == nullptr)
          {
            _root = cur->_right;
          }
          else if (parent->_left == cur)
          {
            parent->_left = cur->_right;
          }
          else if (parent->_right == cur)
          {
            parent->_right = cur->_right;
          }
        }
        else if (cur->_right == nullptr)
        {
          if (parent == nullptr)
          {
            _root = cur->_left;
          }
          else if (parent->_left == cur)
          {
            parent->_left = cur->_left;
          }
          else if (parent->_right = cur)
          {
            parent->_right = cur->_left;
          }
        }
        else
        {
          Node* leftMax = cur->_left;
          Node* leftMaxParent = cur;
          while (leftMax->_right)
          {
            leftMaxParent = leftMax;
            leftMax = leftMax->_right;
          }
          std::swap(cur->_key, leftMax->_key);
          if (leftMaxParent->_left == leftMax)
          {
            leftMaxParent->_left = leftMax->_left;
          }
          else
          {
            leftMaxParent->_right = leftMax->_left;
          }
          cur = leftMax;
        }
        delete cur;
        return true;
      }
    }
  }
  bool FindR(const K& key)
  {
    return _FindR(_root, key);
  }
  bool InsertR(const K& key)
  {
    return _InsertR(_root, key);
  }
  bool EraseR(const K& key)
  {
    return _EraseR(_root, key);
  }
private:
  Node* Copy(Node* root)
  {
    if (root == nullptr)
    {
      return nullptr;
    }
    Node* Copyroot = new Node(root->_key);
    Copyroot->_left = Copy(root->_left);
    Copyroot->_right = Copy(root->_right);
    return Copyroot;
  }
  void _Destory(Node*& root)
  {
    if (root == nullptr)
    {
      return;
    }
    _Destory(root->_left);
    _Destory(root->_right);
    delete root;
    root == nullptr;
  }
  bool _EraseR(Node*& root, const K& key)
  {
    if (root == nullptr)
    {
      return false;
    }
    if (root->_key < key)
    {
      return _EraseR(root->_right, key);
    }
    else if (root->_key > key)
    {
      return _EraseR(root->_left, key);
    }
    else
    {
      Node* del = root;
      if (root->_left == nullptr)
      {
        root = root->_right;
      }
      else if (root->_right == nullptr)
      {
        root = root->_left;
      }
      else
      {
        Node* leftMax = root->_left;
        while (leftMax->_right)
        {
          leftMax = leftMax->_right;
        }
        std::swap(leftMax->_key, root->_key);
        return _EraseR(root->_left, key);
      }
      delete del;
      return true;
    }
  }
  bool _InsertR(Node*& root, const K& key)
  {
    if (root == nullptr)
    {
      root = new Node(key);
      return true;
    }
    if (root->_key < key)
    {
      return _InsertR(root->_right, key);
    }
    else if (root->_key > key)
    {
      return _InsertR(root->_left, key);
    }
    else
    {
      return false;
    }
  }
  bool _FindR(Node* root, const K& key)
  {
    if (root == nullptr)
    {
      return false;
    }
    if (root->_key == key)
    {
      return true;
    }
    else if (root->_key > key)
    {
      return _FindR(root->_left, key);
    }
    else
    {
      return  _FindR(root->_right, key);
    }
  }
  void _InOrder(Node* root)
  {
    if (root == nullptr)
    {
      return;
    }
    _InOrder(root->_left);
    cout << root->_key << " ";
    _InOrder(root->_right);
  }
private:
  Node* _root;
};

对于上面的代码,我们可以使用如下代码进行测试

#include<iostream>
#include<algorithm>
using namespace std;
#include"BinarySearchTree.h"
void Test1()
{
  BSTree<int> root;
  int arr[] = { 8,3,1,10,6,4,7,14,13,5,10,15,20,21,22,32,2 };
  for (auto& e : arr)
  {
    root.Insert(e);
  }
  root.InOrder();
  cout << endl;
  for (auto& e : arr)
  {
    root.Erase1(e);
    root.InOrder();
    cout << endl;
  }
  BSTree<int> root2;
  for (auto& e : arr)
  {
    root2.Insert(e);
  }
  root2.InOrder();
  cout << endl;
  for (auto& e : arr)
  {
    root2.Erase2(e);
    root2.InOrder();
    cout << endl;
  }
  cout << root2.FindR(1) << endl;
}
void Test2()
{
  BSTree<int> root;
  int arr[] = { 8,3,1,10,6,4,7,14,13,5,10,15,20,21,22,32,2 };
  for (auto& e : arr)
  {
    root.InsertR(e);
  }
  root.InOrder();
  cout << endl;
  cout << root.FindR(1) << endl;
  for (auto& e : arr)
  {
    root.EraseR(e);
    root.InOrder();
    cout << endl;
  }
  cout << root.FindR(1) << endl;
}
void Test3()
{
  BSTree<int> root;
  int arr[] = { 8,3,1,10,6,4,7,14,13,5 };
  for (auto& e : arr)
  {
    root.InsertR(e);
  }
  root.InOrder();
  cout << endl;
  BSTree<int> r1(root);
  r1.InOrder();
  cout << endl;
  BSTree<int> t2 = r1;
  t2.InOrder();
}
int main()
{
  Test3();
  return 0;
}

最终结果都是符合我们的预期的。


总结

本文详细介绍了二叉搜索树的概念和实现,二叉搜索树的删除是最麻烦的,因为它的考虑的情况确实是非常多的,也确实是比较难的。希望读者可以好好品味

相关文章
|
6月前
|
C++
二叉树进阶面试题(精华总结)【C++版本】
二叉树进阶面试题(精华总结)【C++版本】
|
6月前
|
存储 编译器 数据库
【C/C++ 数据结构 】线索二叉树全解析:从数学原理到C++实现
【C/C++ 数据结构 】线索二叉树全解析:从数学原理到C++实现
133 0
|
6月前
|
算法 C++ 开发者
【C/C++ 数据结构 】二叉树基本性质:对于任何一颗二叉树T,若其终端结点为n0 ,那么度数为2的结点数为n2。则n0=n2+1...
【C/C++ 数据结构 】二叉树基本性质:对于任何一颗二叉树T,若其终端结点为n0 ,那么度数为2的结点数为n2。则n0=n2+1...
76 0
|
1月前
|
存储 算法 C++
【搜索算法】 跳马问题(C/C++)
【搜索算法】 跳马问题(C/C++)
|
1月前
|
人工智能 算法 Java
【搜索算法】数字游戏(C/C++)
【搜索算法】数字游戏(C/C++)
|
4月前
|
存储 C++
【C++】二叉树进阶之二叉搜索树(下)
【C++】二叉树进阶之二叉搜索树(下)
32 4
|
4月前
|
Java 编译器 C++
【C++】二叉树进阶之二叉搜索树(上)
【C++】二叉树进阶之二叉搜索树(上)
36 3
|
4月前
|
算法 C++
【C++高阶】高效搜索的秘密:深入解析搜索二叉树
【C++高阶】高效搜索的秘密:深入解析搜索二叉树
42 2
|
6月前
|
存储 C++
【C++】二叉树进阶 -- 详解
【C++】二叉树进阶 -- 详解
|
6月前
|
存储 算法 数据管理
C++中利用随机策略优化二叉树操作效率的实现方法
C++中利用随机策略优化二叉树操作效率的实现方法
113 1