【C++】二叉搜索树 - 指南

在这里插入图片描述

文章目录

  • 一、概念与性能分析
  • 二、二叉搜索树的实现
    • 1. 结点
    • 2. 默认成员函数
    • 3. 插入
    • 4. 查找
    • 5. 删除
    • 6. 中序遍历
    • 7. 完整实现
  • 三、key和key/value

一、概念与性能分析

二叉搜索树,又称搜索二叉树、二叉排序树,是一种具有特殊性质的二叉树:

  • 左子树上所有结点的值都小于等于根结点的值
  • 右子树上所有结点的值都大于等于根结点的值
  • 它的左右子树也都是二叉搜索树
  • 二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看场景需求

由于它本身和它的子树都是二叉搜索树,不难想象:二叉搜索树的中序遍历一定是升序序列

从名字就能看出来,二叉搜索树主要用于搜索。最优情况下,如果它接近完全二叉树的结构,不难分析,最大搜索次数即其树的高度:log2N;最差情况下,如果它接近单支树的结构,则其搜索次数接近结点个数:N。在这里插入图片描述
所以综合来看二叉搜索树的增删查改效率为O(N),这样的时间复杂度还是太高了。必须将二叉搜索树的结构进一步优化,也就是以后要学习的平衡二叉搜索树AVL树、红黑树。

二、二叉搜索树的实现

1. 结点

首先需要定义出结点的结构:

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

2. 默认成员函数

由于树的类中涉及到结点资源的开辟和删除,所以需要我们自己写拷贝构造、赋值重载、析构函数等。

template<
class K>
class BSTree
{
typedef BSTreeNode<K> Node;
  private:
  Node* _root;
  public:
  //......
  }
  • 默认构造函数:
//默认构造一棵空树
BSTree(
)
:_root(
nullptr
)
{
}

之前我们学习链式结构的树,它的很多操作都需要用递归完成,但我们这里的拷贝构造、析构函数没办法传参递归怎么办?我们可以写出递归的函数后,“包装”起来:

  • 拷贝构造函数:
BSTree(
const BSTree<K>
  & t)
  {
  //实际完成拷贝功能的是Copy函数,我们的拷贝构造只是把他包装起来
  _root = Copy(t._root)
  ;
  }
  Node* Copy(Node* root)
  {
  if (root ==
  nullptr
  )
  {
  return
  nullptr
  ;
  }
  Node* newnode =
  new Node(root->_key)
  ;
  //利用递归完成所有结点的拷贝
  newnode->_left = Copy(root->_left)
  ;
  newnode->_right = Copy(root->_right)
  ;
  return newnode;
  }
  • 析构函数:
~BSTree(
)
{
Destory(_root)
;
_root =
nullptr
;
}
void Destory(Node* root)
{
if (root ==
nullptr
)
{
return
;
}
//利用递归完成所有结点的资源释放
Destory(root->_left)
;
Destory(root->_right)
;
delete root;
}

拷贝构造函数的Copy函数和析构函数的Destory函数仅供它们内部使用,所以可以把它们放到private部分

  • 赋值运算符重载:
BSTree<K>
  &
  operator=(BSTree<K> t)
    {
    swap(_root, t._root)
    ;
    return *
    this
    ;
    }

3. 插入

二叉搜索树的插入要按照搜索树的性质寻找正确位置插入:

  • 树为空,则直接新增结点作为root
  • 树不为空,则按照二叉搜索树性质,插入值比当前节点值大就往右走,插入值比当前节点值小则往左走。直到找到空位置,插入新节点:

在这里插入图片描述
代码实现:

bool Insert(
const K& key)
{
if (_root ==
nullptr
)
{
_root =
new Node(key)
;
return true
;
}
//parent保存待插入位置的父结点
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
  {
  //如果key值已存在,则插入失败
  return false
  ;
  }
  }
  //cur找到了正确的位置
  cur =
  new Node(key)
  ;
  if (parent->_key < key)
  {
  parent->_right = cur;
  }
  else
  {
  parent->_left = cur;
  }
  return true
  ;
  }

4. 查找

查找一个值是否存在,也很简单了:

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

5. 删除

二叉搜索树的删除相对复杂,首先查找待删除元素结点,不存在则返回false。
若存在,则显然有以下几种情形:(假设要删除的结点为x)
x没有孩子、有左孩子或右孩子、有两个孩子。
为了删除结点后不破坏搜索二叉树的性质,它们要用不同的解决方法:

  • 若x没有孩子,则直接删除x
  • 若x只有一个孩子,则让x的父亲的原指向x的指针改为指向x的孩子,再删除x
  • 若x有两个孩子,使用替换法:找x的左子树的最大结点l(即左子树的最右结点),或x的右子树的最小结点r(即右子树的最左结点),代替x。因为这两个结点的值替换x后,都还能保持搜索二叉树的性质。最后再删除l或r结点,因为l或r结点一定是没有孩子或只有一个孩子的,再用上面两种情形方法处理就好。

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

代码实现,可以将情形1和情形2整合讨论:

bool Erase(
const K& 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 //找到了待删除结点cur,开始删除
  {
  //如果cur左孩子为空,就连接parent和cur的右子
  if (cur->_left ==
  nullptr
  )
  {
  //特殊情况cur为根结点,cur右孩子当根结点
  if (cur == _root)
  {
  _root = cur->_right;
  }
  else
  {
  //如果cur是parent的左,则让parent的左指向cur的右子
  if (cur == parent->_left)
  {
  parent->_left = cur->_right;
  }
  //如果cur是parent的右,则让parent的右指向cur的右子
  else
  {
  parent->_right = cur->_right;
  }
  }
  delete cur;
  }
  //如果cur右孩子为空,就连接parent和cur的左子
  else
  if (cur->_right ==
  nullptr
  )
  {
  //特殊情况cur为根结点,cur左孩子当根结点
  if (cur == _root)
  {
  _root = cur->_left;
  }
  else
  {
  //如果cur是parent的左,则让parent的左指向cur的左子
  if (cur == parent->_left)
  {
  parent->_left = cur->_left;
  }
  //如果cur是parent的右,则让parent的右指向cur的左子
  else
  {
  parent->_right = cur->_left;
  }
  }
  delete cur;
  }
  else //两个孩子,这里选择找左子树的最大结点替换
  {
  Node* LeftMaxParent = cur;
  Node* LeftMax = cur->_left;
  //找左子树的最右结点
  while (LeftMax->_right)
  {
  LeftMaxParent = LeftMax;
  LeftMax = LeftMax->_right;
  }
  swap(cur->_key, LeftMax->_key)
  ;
  //此时LeftMax一定没有右孩子,可能有左孩子
  //还可能LeftMaxParent就是根结点,所以要判断LeftMax是其父的左还是右孩子 
  if (LeftMaxParent->_left == LeftMax)
  {
  LeftMaxParent->_left = LeftMax->_left;
  }
  else
  {
  LeftMaxParent->_right = LeftMax->_left;
  }
  delete LeftMax;
  }
  return true
  ;
  }
  }
  return false
  ;
  }

6. 中序遍历

为了验证二叉搜索树的性质,我们要判断中序遍历的结果是否是升序的:

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

同样地,将_InOrder函数放在private中。

7. 完整实现

综上,二叉搜索树的完整实现:

namespace lydly
{
template<
class K>
struct BSTreeNode
{
BSTreeNode* _left;
BSTreeNode* _right;
K _key;
BSTreeNode(
const K& key)
:_left(
nullptr
)
,_right(
nullptr
)
,_key(key)
{
}
}
;
template<
class K>
class BSTree
{
typedef BSTreeNode<K> Node;
  private:
  Node* _root;
  public:
  //默认构造一棵空树
  BSTree(
  )
  :_root(
  nullptr
  )
  {
  }
  //拷贝构造
  BSTree(
  const BSTree<K>
    & t)
    {
    _root = Copy(t._root)
    ;
    }
    BSTree<K>
      &
      operator=(BSTree<K> t)
        {
        swap(_root, t._root)
        ;
        return *
        this
        ;
        }
        ~BSTree(
        )
        {
        Destory(_root)
        ;
        _root =
        nullptr
        ;
        }
        bool Insert(
        const K& key)
        {
        if (_root ==
        nullptr
        )
        {
        _root =
        new Node(key)
        ;
        return true
        ;
        }
        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
          {
          //如果key值已存在,则插入失败
          return false
          ;
          }
          }
          //cur找到了正确的位置
          cur =
          new Node(key)
          ;
          if (parent->_key < key)
          {
          parent->_right = cur;
          }
          else
          {
          parent->_left = cur;
          }
          return true
          ;
          }
          bool Find(
          const K& key)
          {
          Node* cur = _root;
          while (cur)
          {
          if (key < cur->_key)
            {
            cur = cur->_left;
            }
            else
            if (key > cur->_key)
            {
            cur = cur->_right;
            }
            else //key == cur->_key找到了
            {
            return true
            ;
            }
            }
            return false
            ;
            }
            bool Erase(
            const K& 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 //找到了待删除结点,开始删除
              {
              //如果cur左孩子为空,就连接parent和cur的右子
              if (cur->_left ==
              nullptr
              )
              {
              //特殊情况cur为根结点,cur右子当根
              if (cur == _root)
              {
              _root = cur->_right;
              }
              else
              {
              //如果cur是parent的左,则让parent的左指向cur的右子
              if (cur == parent->_left)
              {
              parent->_left = cur->_right;
              }
              //如果cur是parent的右,则让parent的右指向cur的右子
              else
              {
              parent->_right = cur->_right;
              }
              }
              delete cur;
              }
              //如果cur右孩子为空,就连接parent和cur的左子
              else
              if (cur->_right ==
              nullptr
              )
              {
              //特殊情况cur为根结点,cur左子当根
              if (cur == _root)
              {
              _root = cur->_left;
              }
              else
              {
              //如果cur是parent的左,则让parent的左指向cur的左子
              if (cur == parent->_left)
              {
              parent->_left = cur->_left;
              }
              //如果cur是parent的右,则让parent的右指向cur的左子
              else
              {
              parent->_right = cur->_left;
              }
              }
              delete cur;
              }
              else //两个孩子,这里选择找左子树的最大结点替换
              {
              Node* LeftMaxParent = cur;
              Node* LeftMax = cur->_left;
              //找左子树的最右结点
              while (LeftMax->_right)
              {
              LeftMaxParent = LeftMax;
              LeftMax = LeftMax->_right;
              }
              swap(cur->_key, LeftMax->_key)
              ;
              //此时LeftMax一定没有右孩子,可能有左孩子
              //还可能LeftMaxParent就是根结点,所以要判断LeftMax是其父的左还是右孩子 
              if (LeftMaxParent->_left == LeftMax)
              {
              LeftMaxParent->_left = LeftMax->_left;
              }
              else
              {
              LeftMaxParent->_right = LeftMax->_left;
              }
              delete LeftMax;
              }
              return true
              ;
              }
              }
              return false
              ;
              }
              void InOrder(
              )
              {
              _InOrder(_root)
              ;
              cout << endl;
              }
              private:
              void Destory(Node* root)
              {
              if (root ==
              nullptr
              )
              {
              return
              ;
              }
              Destory(root->_left)
              ;
              Destory(root->_right)
              ;
              delete root;
              }
              void _InOrder(Node* root)
              {
              if (root ==
              nullptr
              )
              {
              return
              ;
              }
              _InOrder(root->_left)
              ;
              cout << root->_key <<
                " "
                ;
                _InOrder(root->_right)
                ;
                }
                Node* Copy(Node* root)
                {
                if (root ==
                nullptr
                )
                {
                return
                nullptr
                ;
                }
                Node* newnode =
                new Node(root->_key)
                ;
                //利用递归完成所有结点的拷贝
                newnode->_left = Copy(root->_left)
                ;
                newnode->_right = Copy(root->_right)
                ;
                return newnode;
                }
                }
                ;
                }

简单测试:
在这里插入图片描述
没有问题。

三、key和key/value

上面我们实现的二叉搜索树,结点中只存储了一个key,称为关键码,关键码即为需要搜索的值,使用这种二叉搜索树的场景只需要判断key是否存在。key的搜索场景实现的二叉搜索树支持增删查,不支持修改,因为修改key值破坏二叉搜索树性质了。

使用这种key二叉搜索树的场景,比如小区车库,只有业主的车能进入,车牌号作为key值,就在系统中检查key是否存在以确定是否为业主的车。

还有一种key/value的搜索场景:每一个关键码key,都有与之对应的value,value可以为任意类型对象,增删查还是以key为关键码在二叉搜索树中操作,修改不能改key,但是可以修改value。

使用这种key/value二叉搜索树的场景,比如英语词典,树的结构中存储key(英文)和value(中文),搜索时输入英文作为key进行查找,找到后输出对应的value中文。

key/value二叉搜索树的实现:

namespace lydly
{
template<
class K
,
class V>
struct BSTreeNode
{
BSTreeNode<K, V>
  * _left;
  BSTreeNode<K, V>
    * _right;
    K _key;
    V _value;
    BSTreeNode(
    const K& key,
    const V& value)
    :_left(
    nullptr
    )
    , _right(
    nullptr
    )
    , _key(key)
    , _value(value)
    {
    }
    }
    ;
    template<
    class K
    ,
    class V>
    class BSTree
    {
    typedef BSTreeNode<K, V> Node;
      private:
      Node* _root =
      nullptr
      ;
      public:
      BSTree(
      )
      {
      }
      BSTree(
      const BSTree<K, V>
        & t)
        {
        _root = Copy(t._root)
        ;
        }
        BSTree<K, V>
          &
          operator=(BSTree<K, V> t)
            {
            swap(_root, t._root)
            ;
            return *
            this
            ;
            }
            ~BSTree(
            )
            {
            Destory(_root)
            ;
            _root =
            nullptr
            ;
            }
            bool Insert(
            const K& key,
            const V& value)
            {
            if (_root ==
            nullptr
            )
            {
            _root =
            new Node(key, value)
            ;
            return true
            ;
            }
            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
            {
            return false
            ;
            }
            }
            cur =
            new Node(key, value)
            ;
            if (parent->_key < key)
            {
            parent->_right = cur;
            }
            else
            {
            parent->_left = cur;
            }
            return true
            ;
            }
            Node* 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 cur;
            }
            }
            return
            nullptr
            ;
            }
            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 (cur == parent->_left)
            {
            parent->_left = cur->_right;
            }
            else
            {
            parent->_right = cur->_right;
            }
            }
            delete cur;
            }
            else
            if (cur->_right ==
            nullptr
            )
            {
            if (cur == _root)
            {
            _root = cur->_left;
            }
            else
            {
            if (cur == parent->_left)
            {
            parent->_left = cur->_left;
            }
            else
            {
            parent->_right = cur->_left;
            }
            }
            delete cur;
            }
            else // 两个孩子
            {
            Node* pMinRight = cur;
            Node* minRight = cur->_right;
            while (minRight->_left)
            {
            pMinRight = minRight;
            minRight = minRight->_left;
            }
            swap(cur->_key, minRight->_key)
            ;
            if (pMinRight->_left == minRight)
            {
            pMinRight->_left = minRight->_right;
            }
            else
            {
            pMinRight->_right = minRight->_right;
            }
            delete minRight;
            }
            return true
            ;
            }
            }
            return false
            ;
            }
            void InOrder(
            )
            {
            _InOrder(_root)
            ;
            cout << endl;
            }
            private:
            void _InOrder(Node* root)
            {
            if (root ==
            nullptr
            )
            {
            return
            ;
            }
            _InOrder(root->_left)
            ;
            cout << root->_key <<
              ":" << root->_value << endl;
                _InOrder(root->_right)
                ;
                }
                void Destory(Node* root)
                {
                if (root ==
                nullptr
                )
                {
                return
                ;
                }
                Destory(root->_left)
                ;
                Destory(root->_right)
                ;
                delete root;
                }
                Node* Copy(Node* root)
                {
                if (root ==
                nullptr
                )
                return
                nullptr
                ;
                Node* copy =
                new Node(root->_key, root->_value)
                ;
                copy->_left = Copy(root->_left)
                ;
                copy->_right = Copy(root->_right)
                ;
                return copy;
                }
                }
                ;
                }

本篇完,感谢阅读。

posted @ 2025-07-20 15:12  yfceshi  阅读(8)  评论(0)    收藏  举报