C++-你知道二叉搜索树吗?(循环版)

简介: C++-你知道二叉搜索树吗?(循环版)

1.二叉搜索树


1.1 二叉搜索树概念

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


若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

它的左右子树也分别为二叉搜索树

66b0e3d6ca644f1cb3fbb53d049a69ab.png

在二叉搜索树中,右子树上的任意一个节点的值都大于当前节点的值,左子树上的任意一个节点的值都小于当前节点的值,所以查找值的时候效率就很高,在任意位置插入和删除数据也不需要挪动,而且搜索二叉树走中序遍历就是一个升序。


1.2 二叉搜索树操作

首先将节点创建出来。

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

e0619a70fae54d41b73cc882eae19b55.png

1.2.1. 二叉搜索树的查找

a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。


b、最多查找高度次,走到到空,还没找到,这个值不存在。


查找的逻辑还是比较简单的,就是比较值,大于当前节点的值则继续往右边找,小于当前节点的值则继续往左边找,相等则返回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
    return true;
  }
  return false;
  }

1.2.2. 二叉搜索树的插入

首先我们要判断一下这个二叉搜索树是不是空的,如果是空的就直接new一个节点当成头节点就行。假设要插入一个16,那么需要一个父节点parent来记录上一个节点,因为当cur为空时,需要把16这个节点插入到父节点的左子树或者右子树里面去,那么我们就需要比较大小。因为16大于父节点14,所以插入到父节点的右子树。

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
    return true;
  }
  cur = new Node(key);
  if (parent->_key < key)
    parent->_right = cur;
  else
    parent->_left = cur;
  return true;
  }

925893bf460946828e8f2e28531a4813.png 1.2.3. 二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面三种情况:


情况a:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除


情况b:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除


情况c:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题--替换法删除


需要注意的是情况a和b有个例外,就是要删除的节点是根节点,那么另外一颗树就为空,此时需要将根节点更新为另外一颗子树的根。


假设要删除8,那么需要将根节点root更新为8的左节点3.

c2bbc72b58a14a559b0a8adad88ecece.png


假设要删除8,那么需要将根节点root更新为8的右节点10.

6be74ca43b914a68b7e74c5ccff81151.png

接下来我来给大家分析一下这三种情况:


情况a:


这种情况就是当前要删除的节点只有左孩子,那么删除之前要保存父节点,然后判断当前节点是父节点的左孩子还是右孩子,删除之后将左孩子成为父节点的左/右孩子。给大家举个例子:


假设我们要删除14这个节点,那么我们只需要判断14是10的左孩子还是右孩子,如果14是左孩子就将13成为10的左孩子,如果14是右孩子,就将13成为10的右孩子。

293906533adc44b4a74b6aad3601f491.png

情况b:


这种情况就是当前要删除的节点只有右孩子,那么删除之前要保存父节点,然后判断当前节点是父节点的左孩子还是右孩子,删除之后将右孩子成为父节点的左/右孩子。给大家举个例子:


假设我们要删除10这个节点,那么我们只需要判断10是8的左孩子还是右孩子,如果10是左孩子就将14成为8的左孩子,如果10是右孩子,就将14成为8的右孩子。

5f3b250a48884258a6d1a14b448e2f21.png

情况c:


这种情况就是当前要删除的节点既有右孩子又有左孩子,那么就需要使用替换法删除。


假设要删除3,那么需要找一个能够替代3的值,那么就需要到右子树找一个最小的值(最右节点),或者去左子树找一个最大的值(最左节点)来替换,因为这样的值替换才能满足二叉搜索树的概念。那么我们去右子树找最小的值,就找到了4.


找到4之后将cur的值3改为4,然后判断4是6的左孩子还是右孩子,因为rightMin不一定是左孩子,比如要删除8,8的rightMin是右孩子10. ,最后删除掉rightMin这个节点。


e0791619f49c4af58a2e7146e4545a41.png


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->_right)
        parent->_right = cur->_right;
        else
        parent->_left = cur->_right;
      }
      delete cur;
      return true;
      }
      else if (cur->_right == nullptr)
      {
      if (cur == _root)
        _root = cur->_left;
      else
      {
        if (cur == parent->_right)
        parent->_right = cur->_left;
        else
        parent->_left = cur->_left;
      }
      delete cur;
      return true;
      }
      else
      {
      // 替换法
      Node* rightMinParent = cur;
      Node* rightMin = cur->_right;
      while (rightMin->_left)
      {
        rightMinParent = rightMin;
        rightMin = rightMin->_left;
      }
      cur->_key = rightMin->_key;
      if (rightMin == rightMinParent->_left)
        rightMinParent->_left = rightMin->_right;
      else
        rightMinParent->_right = rightMin->_right;
      delete rightMin;
      return true;
      }
    }
    }
    return false;
  }

1.2.4 二叉搜索树节点个数

int Size(Node* _root)
  {
    if (_root == nullptr) return 0;
    int lsize = Size(_root->_left);
    int rsize = Size(_root->_right);
    return lsize + rsize + 1;
  }

1.2.5 二叉搜索树叶子节点个数

int LeafSize(Node* _root)
  {
    if (_root == nullptr) return 0;
    if (_root->_left == nullptr && _root->_right == nullptr)
    return 1;
    return LeafSize(_root->_left) + LeafSize(_root->_right);
  }


今天的分享到这里就结束了,感谢大家的阅读!

相关文章
|
3月前
|
算法 测试技术 C++
【C++】map&set的底层结构 -- AVL树(高度平衡二叉搜索树)(下)
【C++】map&set的底层结构 -- AVL树(高度平衡二叉搜索树)(下)
|
3月前
|
C++ 容器
【C++】map&set的底层结构 -- AVL树(高度平衡二叉搜索树)(上)
【C++】map&set的底层结构 -- AVL树(高度平衡二叉搜索树)(上)
|
1月前
|
存储 C++
【C++】二叉树进阶之二叉搜索树(下)
【C++】二叉树进阶之二叉搜索树(下)
18 4
|
1月前
|
Java 编译器 C++
【C++】二叉树进阶之二叉搜索树(上)
【C++】二叉树进阶之二叉搜索树(上)
19 3
|
1月前
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
|
1月前
|
算法 测试技术 C++
【C++高阶】掌握AVL树:构建与维护平衡二叉搜索树的艺术
【C++高阶】掌握AVL树:构建与维护平衡二叉搜索树的艺术
20 2
|
2月前
|
算法 程序员 编译器
C++的四类循环分享
C++的四类循环:Entry or Exit controlled, Ranged-based or For_each
|
2月前
|
存储 C++
【C++航海王:追寻罗杰的编程之路】一篇文章带你了解二叉搜索树
【C++航海王:追寻罗杰的编程之路】一篇文章带你了解二叉搜索树
27 1
|
2月前
|
C++
C++一分钟之-循环结构:for与while循环
【6月更文挑战第18天】在C++中,`for`循环适合已知迭代次数,如数组遍历;`while`循环适用于条件驱动的未知次数循环。`for`以其初始化、条件和递增三部分结构简洁处理重复任务,而`while`则在需要先检查条件时更为灵活。常见错误包括无限循环和逻辑错误,解决办法是确保条件更新和正确判断。了解两者应用场景及陷阱,能提升代码效率和可读性。
39 6
|
2月前
|
C语言 C++ 容器
c++primer plus 6 读书笔记 第五章 循环和关系表达式
c++primer plus 6 读书笔记 第五章 循环和关系表达式