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);
  }


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

相关文章
|
2月前
|
设计模式 测试技术 编译器
C++项目中打破循环依赖的锁链:实用方法大全(一)
C++项目中打破循环依赖的锁链:实用方法大全
92 0
|
2月前
|
缓存 编译器 数据处理
【C/C++ 性能优化】循环展开在C++中的艺术:提升性能的策略与实践
【C/C++ 性能优化】循环展开在C++中的艺术:提升性能的策略与实践
60 0
|
1天前
|
存储 C语言 C++
数据结构/C++:二叉搜索树
数据结构/C++:二叉搜索树
9 1
|
4天前
|
存储 算法 编译器
【C++入门到精通】C++入门 —— 红黑树(自平衡二叉搜索树)
【C++入门到精通】C++入门 —— 红黑树(自平衡二叉搜索树)
11 1
|
4天前
|
存储 机器学习/深度学习 算法
【C++入门到精通】C++入门 —— AVL 树(自平衡二叉搜索树)
【C++入门到精通】C++入门 —— AVL 树(自平衡二叉搜索树)
10 2
|
11天前
|
安全 编译器 程序员
【C++入门】内联函数、auto与基于范围的for循环
【C++入门】内联函数、auto与基于范围的for循环
|
12天前
|
C++ Python
C++教学——从入门到精通 10.循环
学习编程建议先Python后C++,以避免C++思维影响。课程涵盖for、while和do while循环。for循环示例:`for(int i=0;i&lt;n;i++)`,用于计算114514天后的金币总数(1145140个)。死循环通过`for(int i=0;;i++)`实现,用`break`退出。while循环格式`while(条件)`,同样可解决金币问题。do while循环特点是先执行后判断,结构为`do{...}while(条件)`。
21 2
|
17天前
|
C++
【C++高阶(一)】二叉搜索树深度剖析
【C++高阶(一)】二叉搜索树深度剖析
|
18天前
|
Linux C++
c++的学习之路:24、 二叉搜索树概念
c++的学习之路:24、 二叉搜索树概念
31 1
|
1月前
|
C++
C++ While 和 For 循环:流程控制全解析
本文介绍了C++中的`switch`语句和循环结构。`switch`语句根据表达式的值执行匹配的代码块,可以使用`break`终止执行并跳出`switch`。`default`关键字用于处理没有匹配`case`的情况。接着,文章讲述了三种类型的循环:`while`循环在条件满足时执行代码,`do/while`至少执行一次代码再检查条件,`for`循环适用于已知循环次数的情况。`for`循环包含初始化、条件和递增三个部分。此外,还提到了嵌套循环和C++11引入的`foreach`循环,用于遍历数组元素。最后,鼓励读者关注微信公众号`Let us Coding`获取更多内容。
21 0