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

六、思维导图


目录
相关文章
|
4天前
|
存储 C++
【C++航海王:追寻罗杰的编程之路】一篇文章带你了解二叉搜索树
【C++航海王:追寻罗杰的编程之路】一篇文章带你了解二叉搜索树
9 1
|
9天前
|
C++
C++一分钟之-继承与多态概念
【6月更文挑战第21天】**C++的继承与多态概述:** - 继承允许类从基类复用代码,增强代码结构和重用性。 - 多态通过虚函数实现,使不同类对象能以同一类型处理。 - 关键点包括访问权限、构造/析构、菱形问题、虚函数与动态绑定。 - 示例代码展示如何创建派生类和调用虚函数。 - 注意构造函数初始化、空指针检查和避免切片问题。 - 应用这些概念能提升程序设计和维护效率。
20 2
|
17天前
|
编译器 C++
C++学习笔记(二开始学习C++)
C++学习笔记(二开始学习C++)
|
15天前
|
存储 编译器 程序员
【C++高阶】C++继承学习手册:全面解析继承的各个方面
【C++高阶】C++继承学习手册:全面解析继承的各个方面
16 1
|
17天前
|
编译器 C语言 C++
c++primer plus 6 读书笔记 第二章 开始学习C++
c++primer plus 6 读书笔记 第二章 开始学习C++
|
4天前
|
C++
【c++】二叉搜索树
【c++】二叉搜索树
9 0
|
4天前
|
C++
【C++】学习笔记——二叉搜索树
【C++】学习笔记——二叉搜索树
8 0
|
6天前
|
存储 编译器 程序员
C++语言基础学习
C++语言基础学习
|
6天前
|
安全 API C++
逆向学习Windows篇:C++中多线程的使用和回调函数的实现
逆向学习Windows篇:C++中多线程的使用和回调函数的实现
9 0
|
6天前
|
C++ UED 开发者
逆向学习 MFC 篇:视图分割和在 C++ 的 Windows 窗口程序中添加图标的方法
逆向学习 MFC 篇:视图分割和在 C++ 的 Windows 窗口程序中添加图标的方法
7 0