c++的学习之路:24、 二叉搜索树概念

简介: c++的学习之路:24、 二叉搜索树概念

一、二叉搜索树概念

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

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

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

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

如下图所示的图片就是一个二叉搜索树。

二、 二叉搜索树操作

这个就是不在附图了,就是上面那个图,他的数值就是int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};这个数组所示的数值。

1、二叉搜索树的查找

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

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

2、二叉搜索树的插入

a、树为空,则直接新增节点,赋值给root指针

b、树不空,按二叉搜索树性质查找插入位置,插入新节点

如下图所示

3、二叉搜索树的删除

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

况:

a、要删除的结点无孩子结点

b、要删除的结点只有左孩子结点

c、要删除的结点只有右孩子结点

d、要删除的结点有左、右孩子结点

根据上述情况有下面几种情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除,情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除,情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题--替换法删除,也就是找个保姆

三、二叉搜索树的实现

1、插入

想要插入首先要构建节点,如下方块种代码,就是申请一个节点,这个就不用多说了,第二个快的代码就是申请插入,就是如果没有也就是空的时候就申请一个节点,然后判断需要插入的数值,如果大于跟节点就给给左,如果小于就是右边,然后在循环中进行下去,直到找到合适的位置进行插入,如果有相同的就返回false,测试插入成功在中序遍历进行打印查看。


template<class T>

struct BSTreeNode

{

   BSTreeNode<T>* _left;

   BSTreeNode<T>* _right;

   T _key;

   BSTreeNode(const T& key)

       :_left(nullptr)

       , _right(nullptr)

       , _key(key)

   {}

};


typedef BSTreeNode<K> Node;

   bool Insert(const K& key)

   {

       if (_root == nullptr)

       {

           _root = new Node(key);

           return true;

       }

       Node* parent = nullptr;

       Node* cur = _root;

       while (cur)

       {

           if (cur->_key > key)

           {

               parent = cur;

               cur = cur->_left;

           }

           else if (cur->_key < key)

           {

               parent = cur;

               cur = cur->_right;

           }

           else

           {

               return false;

           }

       }

       cur = new Node(key);

       if (parent->_key > key)

       {

           parent->_left = cur;

       }

       else

       {

           parent->_right = cur;

       }

       return true;

   }  

2、中序遍历

在下方块种的代码就是测试中序的,这个就没啥说的就是递归打印,但是有一点要注意,root节点是私密的在外面访问不到,没法使用,这里需要借用一个没有参数的函数进行打印,如下方代码所示,测试结果如图。

void InOrder()

   {

       _InOrder(_root);

   }

   void _InOrder(Node* root)

   {

       if (root == nullptr)

           return;

       _InOrder(root->_left);

       cout << root->_key << ' ';

       _InOrder(root->_right);

   }

3、删除

这个就是优点麻烦了,他就是和上面所写操作中的三种情况,根据这三种情况进行写,首先就是寻找相等的,这个就没啥说的了,找到后就是判断左边为空和右边为空的两种情况,找到相等的时候,然后进行判断是否需要链接,这里是不管需不需要都进行链接,因为一种是后续有节点进行连接,一种是没有直接连接为空,刚好解决这两中情况,然后就是左右都不为空的时候,这个需要找个托孤的,就有点像我学Linux的时候遇到的孤儿进程被1号进程托孤一样,这里就是左边最大节点和右边最小节点,然后进行换一下,在进行删除,这里代码如下,测试如图。


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 (parent->_left == cur)//如果cur在父节点的左边,就把cur右边的值托孤给父,如果是空,刚好是空节点给父

                       {

                           parent->_left = cur->_right;

                       }

                       else//如果cur在父节点的右边,就把cur左边的值托孤给父

                       {

                           parent->_right = cur->_right;

                       }


                   }

                   delete cur;

               }

               //右为空

               else if (cur->_right == nullptr)

               {

                   if (cur == _root)//如果是根节点,右边都是空,只有左边有值

                   {

                       _root = cur->_left;

                   }

                   else//不是根节点

                   {

                       if (parent->_left = cur)//如果cur在父节点的左边,就把cur右边的值托孤给父,如果是空,刚好是空节点给父

                       {

                           parent->_left = cur->_left;

                       }

                       else//如果cur在父节点的右边,就把cur左边的值托孤给父

                       {

                           parent->_right = cur->_left;

                       }


                   }

                   delete cur;

               }

               else

               {

                   // 找右树最小节点替代,也可以是左树最大节点替代

                   Node* pminRight = cur;

                   Node* minRight = cur->_right;

                   while (minRight->_left)

                   {

                       pminRight = minRight;

                       minRight = minRight->_left;

                   }


                   cur->_key = minRight->_key;


                   if (pminRight->_left == minRight)

                   {

                       pminRight->_left = minRight->_right;

                   }

                   else

                   {

                       pminRight->_right = minRight->_right;

                   }

                   delete minRight;

               }

               return true;

           }

       }

       return false;

   }

4、查找

这个就是直接进行查找就好了,比根节点大就去右边找,小就是左边找,如下代码所示,测试如图。

bool Find(const K& key)

   {

       Node* cur = _root;

       while (cur)

       {

           if (cur->_key > key)

           {

               cur->_left;

           }

           else if(cur->_key<key)

           {

               cur->_right;

           }

           else

           {

               cout << cur->_key << endl;

               return true;

           }

       }

       return false;

   }

四、二叉搜索树的递归实现

1、插入

这里世界利用递归去插入,如果为空就创建一个节点,没有的判断是否比根小,小的话就去左边递归,大的就去右边递归,因为这个需要root节点所以和上面所说的中序一样进行利用函数进行使用直接传递key值就可以了,如下代码,这里的测试放在最后了,和上面测试一样的。


bool InsertR(const K& key)

   {

       return _InsertR(_root, key);

   }


bool InsertR(const K& key)

   {

       return _InsertR(_root, key);

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

       }

   }

2、删除

这个删除和上面差不多也是三种情况,这里也是利用递归去查找,就是在最后一种需要注意下,这里是利用左边最大的节点交换,然后进行递归查找,但是需要注意这里需要利用引用传值进行删除。

bool EraseR(const K& key)

   {

       return _EraseR(_root, key);

   }


   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->_right == nullptr)

           {

               root = root->_left;

           }

           else if (root->_left == nullptr)

           {

               root = root->_right;

           }

           else

           {

               Node* maxleft = root->_left;

               while (maxleft->_right)

               {

                   maxleft = maxleft->_right;

               }


               swap(root->_key, maxleft->_key);


               return _EraseR(root->_left, key);

           }


           delete del;


           return true;

       }

   }


3、查找

这个递归的查找就是判断,如果没有找到节点为空的时候就返回false,找到了就返回true,然后大于的话就去左边,小于就去右边,如下方代码所示,测试如图。


bool FindR(const K& key)

   {

       return _FindR(_root, key);

   }


   bool _FindR(Node* root, const K& key)

   {

       if (root == nullptr)

           return false;


       if (root->_key == key)

           return true;


       if (root->_key < key)

           return _FindR(root->_right, key);

       else

           return _FindR(root->_left, key);

   }

五、代码

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
using namespace std;
#include "BSTree.h"
 
//void Test()
//{
//  int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
//  BSTree<int> t1;
//  
//  for (auto e : a)
//  {
//    t1.Insert(e);
//  }
//  t1.Find(8);
//  t1.InOrder();
//  cout << endl;
//
//  for (auto e : a)
//  {
//    t1.Erase(e);
//    t1.InOrder();
//    cout << endl;
//  }
//
//  t1.InOrder();
//  cout << endl;
//}
 
void Test()
{
  int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
  BSTree<int> t1;
  
  for (auto e : a)
  {
    t1.InsertR(e);
  }
  cout<<t1.FindR(8)<<endl;
  t1.InOrder();
  cout << endl;
 
  for (auto e : a)
  {
    t1.EraseR(e);
    t1.InOrder();
    cout << endl;
  }
 
  t1.InOrder();
  cout << endl;
}
 
int main()
{
  Test();
  return 0;
}
 

BSTree.h

#pragma once
 
template<class T>
struct BSTreeNode
{
  BSTreeNode<T>* _left;
  BSTreeNode<T>* _right;
  T _key;
  BSTreeNode(const T& key)
    :_left(nullptr)
    , _right(nullptr)
    , _key(key)
  {}
};
template<class K>
class BSTree
{
public:
  typedef BSTreeNode<K> Node;
  BSTree() = default; // 制定强制生成默认构造
 
  BSTree(const BSTree<K>&t)
  {
    _root = Copy(t._root);
  }
 
  BSTree<K>& operator=(BSTree<K> t)
  {
    swap(_root, t._root);
    return *this;
  }
 
  ~BSTree()
  {
    Destroy(_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 (cur->_key > key)
      {
        parent = cur;
        cur = cur->_left;
      }
      else if (cur->_key < key)
      {
        parent = cur;
        cur = cur->_right;
      }
      else
      {
        return false;
      }
    }
    cur = new Node(key);
    if (parent->_key > key)
    {
      parent->_left = cur;
    }
    else
    {
      parent->_right = cur;
    }
    return true;
  }
  bool Find(const K& key)
  {
    Node* cur = _root;
    while (cur)
    {
      if (cur->_key > key)
      {
        cur->_left;
      }
      else if(cur->_key<key)
      {
        cur->_right;
      }
      else
      {
        cout << cur->_key << endl;
        return true;
      }
    }
    return false;
  }
  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 (parent->_left == cur)//如果cur在父节点的左边,就把cur右边的值托孤给父,如果是空,刚好是空节点给父
            {
              parent->_left = cur->_right;
            }
            else//如果cur在父节点的右边,就把cur左边的值托孤给父
            {
              parent->_right = cur->_right;
            }
 
          }
          delete cur;
        }
        //右为空
        else if (cur->_right == nullptr)
        {
          if (cur == _root)//如果是根节点,右边都是空,只有左边有值
          {
            _root = cur->_left;
          }
          else//不是根节点
          {
            if (parent->_left = cur)//如果cur在父节点的左边,就把cur右边的值托孤给父,如果是空,刚好是空节点给父
            {
              parent->_left = cur->_left;
            }
            else//如果cur在父节点的右边,就把cur左边的值托孤给父
            {
              parent->_right = cur->_left;
            }
 
          }
          delete cur;
        }
        else
        {
          // 找右树最小节点替代,也可以是左树最大节点替代
          Node* pminRight = cur;
          Node* minRight = cur->_right;
          while (minRight->_left)
          {
            pminRight = minRight;
            minRight = minRight->_left;
          }
 
          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);
  }
  void _InOrder(Node* root)
  {
    if (root == nullptr)
      return;
    _InOrder(root->_left);
    cout << root->_key << ' ';
    _InOrder(root->_right);
  }
 
  void Destroy(Node*& root)
  {
    if (root == nullptr)
      return;
 
    Destroy(root->_left);
    Destroy(root->_right);
 
    delete root;
    root = nullptr;
  }
 
  bool FindR(const K& key)
  {
    return _FindR(_root, key);
  }
 
  bool _FindR(Node* root, const K& key)
  {
    if (root == nullptr)
      return false;
 
    if (root->_key == key)
      return true;
 
    if (root->_key < key)
      return _FindR(root->_right, key);
    else
      return _FindR(root->_left, key);
  }
 
  bool InsertR(const K& key)
  {
    return _InsertR(_root, key);
  }
 
  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 EraseR(const K& key)
  {
    return _EraseR(_root, key);
  }
 
  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->_right == nullptr)
      {
        root = root->_left;
      }
      else if (root->_left == nullptr)
      {
        root = root->_right;
      }
      else
      {
        Node* maxleft = root->_left;
        while (maxleft->_right)
        {
          maxleft = maxleft->_right;
        }
 
        swap(root->_key, maxleft->_key);
 
        return _EraseR(root->_left, key);
      }
 
      delete del;
 
      return true;
    }
  }
private:
  Node* _root = nullptr;
};

六、思维导图


目录
相关文章
|
2天前
|
算法 网络安全 区块链
2023/11/10学习记录-C/C++对称分组加密DES
本文介绍了对称分组加密的常见算法(如DES、3DES、AES和国密SM4)及其应用场景,包括文件和视频加密、比特币私钥加密、消息和配置项加密及SSL通信加密。文章还详细展示了如何使用异或实现一个简易的对称加密算法,并通过示例代码演示了DES算法在ECB和CBC模式下的加密和解密过程,以及如何封装DES实现CBC和ECB的PKCS7Padding分块填充。
13 4
2023/11/10学习记录-C/C++对称分组加密DES
|
4月前
|
算法 C语言 C++
C++语言学习指南:从新手到高手,一文带你领略系统编程的巅峰技艺!
【8月更文挑战第22天】C++由Bjarne Stroustrup于1985年创立,凭借卓越性能与灵活性,在系统编程、游戏开发等领域占据重要地位。它继承了C语言的高效性,并引入面向对象编程,使代码更模块化易管理。C++支持基本语法如变量声明与控制结构;通过`iostream`库实现输入输出;利用类与对象实现面向对象编程;提供模板增强代码复用性;具备异常处理机制确保程序健壮性;C++11引入现代化特性简化编程;标准模板库(STL)支持高效编程;多线程支持利用多核优势。虽然学习曲线陡峭,但掌握后可开启高性能编程大门。随着新标准如C++20的发展,C++持续演进,提供更多开发可能性。
89 0
|
24天前
|
存储 C++
【C++】二叉搜索树(BST)
二叉搜索树(Binary Search Tree, BST)是一种特殊的二叉树,其每个节点的左子树所有节点值小于该节点值,右子树所有节点值大于该节点值,且左右子树也均为二叉搜索树。BST支持高效的数据查找、插入和删除操作,时间复杂度通常为O(log n)。本文档详细介绍了BST的基本概念、存储结构及其实现,包括迭代和递归两种方式的查找、插入、删除等操作的具体代码示例。
30 3
|
2月前
|
编译器 C语言 C++
配置C++的学习环境
【10月更文挑战第18天】如果想要学习C++语言,那就需要配置必要的环境和相关的软件,才可以帮助自己更好的掌握语法知识。 一、本地环境设置 如果您想要设置 C++ 语言环境,您需要确保电脑上有以下两款可用的软件,文本编辑器和 C++ 编译器。 二、文本编辑器 通过编辑器创建的文件通常称为源文件,源文件包含程序源代码。 C++ 程序的源文件通常使用扩展名 .cpp、.cp 或 .c。 在开始编程之前,请确保您有一个文本编辑器,且有足够的经验来编写一个计算机程序,然后把它保存在一个文件中,编译并执行它。 Visual Studio Code:虽然它是一个通用的文本编辑器,但它有很多插
|
2月前
|
Java 编译器 C++
c++学习,和友元函数
本文讨论了C++中的友元函数、继承规则、运算符重载以及内存管理的重要性,并提到了指针在C++中的强大功能和使用时需要注意的问题。
27 1
|
2月前
|
程序员 C++ 开发者
C++入门教程:掌握函数重载、引用与内联函数的概念
通过上述介绍和实例,我们可以看到,函数重载提供了多态性;引用提高了函数调用的效率和便捷性;内联函数则在保证代码清晰的同时,提高了程序的运行效率。掌握这些概念,对于初学者来说是非常重要的,它们是提升C++编程技能的基石。
26 0
|
5月前
|
JSON Go C++
开发与运维C++问题之在iLogtail新架构中在C++主程序中新增插件的概念如何解决
开发与运维C++问题之在iLogtail新架构中在C++主程序中新增插件的概念如何解决
50 1
|
5月前
|
存储 C++
【C++】二叉树进阶之二叉搜索树(下)
【C++】二叉树进阶之二叉搜索树(下)
35 4
|
5月前
|
Java 编译器 C++
【C++】二叉树进阶之二叉搜索树(上)
【C++】二叉树进阶之二叉搜索树(上)
39 3
|
5月前
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)