基于中序有序的二叉搜索树(上)

简介: 基于中序有序的二叉搜索树

什么是二叉搜索树

二叉搜索树是普通二叉树的升级,普通二叉树除了存储数据以外好像没有别的优势了,但是二叉搜索树不同,如果对搜索树采用中序遍历得到的结果是一串有序的数字。

二叉搜索树又称为二叉排序树,它要么是一棵空树,要么是一棵具有以下特点的树:

1.如果它的左子树不为空,那么它左子树上所有节点的值都小于根节点的值

2.如果它的右子树不为空,那么它右子树上所有节点的值都小于根节点的值

3.它的左右子树也是一棵二叉搜索树

它的结构如下:

template<class K>
struct  BSTreeNode
{
  //树的节点包含它的左子树和右子树指针以及这个节点中的值
  BSTreeNode<K>* _left;
  BSTreeNode<K>* _right;
  K _key;
  //来个构造函数高一下子
  BSTreeNode(int key)
    :_left(nullptr)
    ,_right(nullptr)
    ,_key(key)
  {}
};
class BSTree
{
public:
    typedef BSTreeNode<K> Node;
private:  
    Node*_root;
};

二叉搜索树的中序遍历

因为中序遍历得到的结果是一串有序的数字列,所以对于二叉搜索树而言中序遍历才是王道。但是因为中序遍历要从根节点开始,也就说要给函数传根节点,但是根节点作为成员变量是私有的,所以这里采用了嵌套的方式(将真正的中序遍历函数私有化,放出一个公有的调用接口):

void  Inorder()
    {
      //中序遍历
      _Inorder(_root);
      cout << endl;
    }
  private:
    //因为中序遍历需要根作为参数,为了保持封装,在这里嵌套一下
    void _Inorder(Node *root)
    {
      if (root == nullptr)
        return;
      _Inorder(root->_left);
      cout << root->_key << " ";
      _Inorder(root->_right);
    }

二叉搜索树的查找

在一棵二叉搜索树中搜索一个元素,最坏的结果也就是O(N),但如果这个搜索树一个接近完全二叉树的情况,则只需要查找高度次。

如果是一棵接近完全二叉,查找复杂度为O(logN),目前我学过的查找中只有二分能达到这样的效率,但是二分有诸多限制,反而不如搜索二叉树来的强大。

所以后面还有平衡二叉树等对结果做进一步的限制,能大大的提升查找的效率

查找的非递归写法

在搜索树中查找某一个值,如果这个值比根节点的值要小,就往根的左子树中找;如果比根节点的值要大,就往右子树中找。

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

查找的递归写法

//为了不破坏封装,这个函数要被设置成私有的  
Node* _FindR(Node* root, const K& val)
{
  //递归式查找
  if (root == nullptr)
    return nullptr;
  if (root->_key > val)
  {
    return _FindR(root->_left, val);
  }
  else if (root->_key < val)
  {
    return _FindR(root->_right, val);
  }
  return root;
}
//这个是暴露在外面的公有接口
bool FindR(const K& val)
{
  return _FindR(_root, val) == nullptr ? false : true;
}

二叉搜索树的插入

向搜索树中插入不能破坏搜索树的结构,所以不能插入和树种元素相同的值

非递归

//二叉搜索树中序遍历结果是有序的数列,不允许往其中插入相同的值,插入删除不允许破坏结构
    //它左孩子的值比根小,右孩子比根大
    bool Insert(const K& key)
    {
      //插入,分为空树插入和非空树插入
      if (_root == nullptr)
      {
        _root = new  Node(key);
        return true;
      }
      else
      {
        //如果要插入的这个值比当前值要大就往右边走,否则就往左边走
        Node* cur = _root;
        Node* parent = cur;
        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);
        //还要判断一下把这个节点链接在parent的左边还是右边
        if (parent->_key > key)
          parent->_left = cur;
        else if (parent->_key < key)
          parent->_right = cur;
      }
      return true;
    }

1.如果是向空树中插入,就直接new一个新节点作为根节点

2.如果是向非空树种插入,首先要找到插入位置,如果在寻找位置的时候发现相同值,直接返回false

3.找到合适位置以后,要将该节点与树链接起来,所以要提前准备一个父节点指针标记插入位置的父节点

递归

递归写法的唯一难处就是在于任何标记插入位置的父节点,这里我们可以采用引用的方式,这个引用用到这里真是妙绝

//递归插入的公共接口
bool InsertR(const K&val)
{
  return _InsertR(_root, val);
}
//递归插入的私有函数
bool _InsertR(Node*& root, const K& val)//这里这个引用巨tm牛逼
{
  if (root == nullptr)
  {
    //空就直接插入
    root = new Node(val);
    return true;
  }
  Node* cur = root;
  while (cur)
  {
    if (cur->_key > val)
      return _InsertR(cur->_left, val);
    else if (cur->_key < val)
      return _InsertR(cur->_right, val);
    else
      return false;//不允许相同的元素插入
  }
}

这里为什么给一个引用就能直接链接上呢?主要还是因为这一层的root是上一层root->left或者root->right的别名

二叉搜索树的删除

删除操作是二叉搜索树种最难的一个,因为它涉及到的情况相对比较多

1.如果这个要被删除的节点有一个子树是空树,那么只要将不为空的子树交给被删除节点的父节点即可(这种方法又叫托孤),当然也不能排除这个要被删除的节点是根节点

2.如果这个被删除的节点的左右子树都不为空,那么就不能直接删除,我采用的是替换法删除,找该节点左子树中的最大值或者右子树的最小值作为替换值,然后将替换值的那个节点删除

非递归

bool Erase(const K& val)
{
  //要删除这个节点,首先要找到这个节点
  Node* cur = _root;
  Node* parent =cur;
  while (cur)
  {
    if (cur->_key > val)
    {
      parent = cur;
      cur = cur->_left;
    }
    else if (cur->_key < val)
    {
      parent = cur;
      cur = cur->_right;
    }
    else
    {
      //这是找到节点了,开始删除,删除大概可以分为两种情况:1.它有一个空节点:托孤 2.它没有空节点:替换法删除
      //有一个节点为空,判断是这个节点的哪个节点是空
      if (cur->_left == nullptr)
      {
        //不排除cur是根节点
        if (cur == _root)
                {
          _root = cur->_right;
        }
        else if (parent->_left == cur)//不是根节点,判读一下它是父节点的左孩子还是右孩子
        {
          parent->_left = cur->_right;
        }
        else
        {
          parent->_right = cur->_right;
        }
        delete cur;
      }
      else if (cur->_right == nullptr)
      {
        if (cur == _root)
        {
          _root = cur->_left;
        }
        else if (parent->_left == cur)
        {
          parent->_left = cur->_left;
        }
        else
        {
          parent->_right = cur->_left;
        }
        delete cur;//要把节点释放掉
      }
      else
      {
        //这是最后一种情况,要删除的节点两边都不为空,要找该节点左子树的最大节点或者右子树的最小节点来替换
        Node* minRight = cur->_right;
        while (minRight->_left)
        {
          parent = minRight;
          minRight = minRight->_left;
        }
        //将值替换
        cur->_key = minRight->_key;
        //最左节点可能有右孩子,所以不能直接将最左节点删除
        if (parent->_left == minRight)
          parent->_left = minRight->_right;
        else
          parent->_right = minRight->_right;
        delete minRight;
      }
      return true;
    }
  }
  return false;
}

递归

//递归删除的公共接口
bool EraseR(const K& val)
{
  return _EraseR(_root, val);
}
bool _EraseR(Node*& root, const K& val)
{
  if (root == nullptr)
    return false;
  Node* cur = root;
  Node* parent = cur;
  if (cur->_key > val)
    return _EraseR(root->_left, val);
  else if (cur->_key < val)
    return _EraseR(root->_right, val);
  else
  {
    //找到节点,删除
    Node* del = root;//因为这里用的是引用的原因,不用再去记录父节点
    if (root->_left == nullptr)
      root = root->_right;
    else if (root->_right == nullptr)
      root = root->_left;
    else
    {
      Node* rightMin = root->_right;
      while (rightMin->_left != nullptr)//找到被删除节点的右树最小节点 
      {
        rightMin = rightMin->_left;
      }
      root->_key = rightMin->_key;//找到了交换key
      //对子树进行递归删除
      return _EraseR(root->_right, rightMin->_key);//return表示子树进行删除,结束掉递归
    }
    delete del;
    return true;
  }
}

二叉搜索树的使用场景

k模型

k模型就是以key作为关键码,结构中只需要存储key值即可。key模型的应用场景有很多,比如查找一本书中的错别字(将词库导入树种,再将书种的每个词去树中搜索一遍,找不到是错别字),比如鉴定一个车牌是否是该停车场的用户(只要将登记的车牌导入搜索树中,当有车来的时候将该车的车牌作为key值去树中检索以下即可)等。二叉搜索树就是一种key模型。

#pragma once
#include<iostream>
using namespace std;
//写一个二叉搜索树
namespace wbm
{
  template<class K>
  struct  BSTreeNode
  {
    //树的节点包含它的左子树和右子树指针以及这个节点中的值
    BSTreeNode<K>* _left;
    BSTreeNode<K>* _right;
    K _key;
    //来个构造函数高一下子
    BSTreeNode(int key)
      :_left(nullptr)
      ,_right(nullptr)
      ,_key(key)
    {}
  };
  //有了单个节点,再来搞一下子结构
  template<class K>
  class BSTree
  {
    typedef BSTreeNode<K> Node;
  public:
    ~BSTree()
    {
      //循环遍历释放节点,因为要传根节点,这里也考虑使用嵌套
      Destory(_root);
      _root = nullptr;
    }
    //二叉搜索树中序遍历结果是有序的数列,不允许往其中插入相同的值,插入删除不允许破坏结构
    //它左孩子的值比根小,右孩子比根大
    bool Insert(const K& key)
    {
      //插入,分为空树插入和非空树插入
      if (_root == nullptr)
      {
        _root = new  Node(key);
        return true;
      }
      else
      {
        //如果要插入的这个值比当前值要大就往右边走,否则就往左边走
        Node* cur = _root;
        Node* parent = cur;
        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);
        //还要判断一下把这个节点链接在parent的左边还是右边
        if (parent->_key > key)
          parent->_left = cur;
        else if (parent->_key < key)
          parent->_right = cur;
      }
      return true;
    }
    //递归插入
    bool InsertR(const K&val)
    {
      return _InsertR(_root, val);
    }
    //查找,找到返回节点,找不到返回空
    bool Find(const K& key)
    {
      if (_root == nullptr)
        return false;
      else
      {
        Node* cur = _root;
        while (cur)
        {
          if (cur->_key > key)
            cur = cur->_left;
          else if (cur->_key < key)
            cur = cur->_right;
          else
          {
            return true;
          }
        }
      }
      return false;
    }
    bool FindR(const K& val)
    {
      return _FindR(_root, val) == nullptr ? false : true;
    }
    bool Erase(const K& val)
    {
      //要删除这个节点,首先要找到这个节点
      Node* cur = _root;
      Node* parent =cur;
      while (cur)
      {
        if (cur->_key > val)
        {
          parent = cur;
          cur = cur->_left;
        }
        else if (cur->_key < val)
        {
          parent = cur;
          cur = cur->_right;
        }
        else
        {
          //这是找到节点了,开始删除,删除大概可以分为两种情况:1.它有一个空节点:托孤 2.它没有空节点:替换法删除
          //有一个节点为空,判断是这个节点的哪个节点是空
          if (cur->_left == nullptr)
          {
            //不排除cur是根节点
            if (cur == _root)
            {
              _root = cur->_right;
            }
            else if (parent->_left == cur)//不是根节点,判读一下它是父节点的左孩子还是右孩子
            {
              parent->_left = cur->_right;
            }
            else
            {
              parent->_right = cur->_right;
            }
            delete cur;
          }
          else if (cur->_right == nullptr)
          {
            if (cur == _root)
            {
              _root = cur->_left;
            }
            else if (parent->_left == cur)
            {
              parent->_left = cur->_left;
            }
            else
            {
              parent->_right = cur->_left;
            }
            delete cur;//要把节点释放掉
          }
          else
          {
            //这是最后一种情况,要删除的节点两边都不为空,要找该节点左子树的最大节点或者右子树的最小节点来替换
            Node* minRight = cur->_right;
            while (minRight->_left)
            {
              parent = minRight;
              minRight = minRight->_left;
            }
            //将值替换
            cur->_key = minRight->_key;
            //最左节点可能有右孩子,所以不能直接将最左节点删除
            if (parent->_left == minRight)
              parent->_left = minRight->_right;
            else
              parent->_right = minRight->_right;
            delete minRight;
          }
          return true;
        }
      }
      return false;
    }
    //删除的递归
    bool EraseR(const K& val)
    {
      return _EraseR(_root, val);
    }
    void  Inorder()
    {
      //中序遍历
      _Inorder(_root);
      cout << endl;
    }
  private:
    //因为中序遍历需要根作为参数,为了保持封装,在这里嵌套一下
    void _Inorder(Node *root)
    {
      if (root == nullptr)
        return;
      _Inorder(root->_left);
      cout << root->_key << " ";
      _Inorder(root->_right);
    }
    Node* _FindR(Node* root, const K& val)
    {
      //递归式查找
      if (root == nullptr)
        return nullptr;
      if (root->_key > val)
      {
        return _FindR(root->_left, val);
      }
      else if (root->_key < val)
      {
        return _FindR(root->_right, val);
      }
      return root;
    }
    bool _InsertR(Node*& root, const K& val)//这里这个引用巨tm牛逼
    {
      if (root == nullptr)
      {
        //空就直接插入
        root = new Node(val);
        return true;
      }
      Node* cur = root;
      while (cur)
      {
        if (cur->_key > val)
          return _InsertR(cur->_left, val);
        else if (cur->_key < val)
          return _InsertR(cur->_right, val);
        else
          return false;//不允许相同的元素插入
      }
    }
    bool _EraseR(Node*& root, const K& val)
    {
      if (root == nullptr)
        return false;
      Node* cur = root;
      Node* parent = cur;
      if (cur->_key > val)
        return _EraseR(root->_left, val);
      else if (cur->_key < val)
        return _EraseR(root->_right, val);
      else
      {
        //找到节点,删除
        Node* del = root;//因为这里用的是引用的原因,不用再去记录父节点
        if (root->_left == nullptr)
          root = root->_right;
        else if (root->_right == nullptr)
          root = root->_left;
        else
        {
          Node* rightMin = root->_right;
          while (rightMin->_left != nullptr)//找到被删除节点的右树最小节点 
          {
            rightMin = rightMin->_left;
          }
          root->_key = rightMin->_key;//找到了交换key
          //对子树进行递归删除
          return _EraseR(root->_right, rightMin->_key);//return表示子树进行删除,结束掉递归
        }
        delete del;
        return true;
      }
    }
    void Destory(Node* root)
    {
      if (root == nullptr)
        return;
      Destory(root->_left);
      Destory(root->_right);
      delete root;
    }
  private:
    Node* _root=nullptr; //不写构造,直接给缺省值
  };
}


相关文章
|
6月前
|
存储 算法
数据结构和算法学习记录——二叉树的存储结构&二叉树的递归遍历(顺序存储结构、链表存储结构、先序中序后序递归遍历)
数据结构和算法学习记录——二叉树的存储结构&二叉树的递归遍历(顺序存储结构、链表存储结构、先序中序后序递归遍历)
93 0
数据结构和算法学习记录——二叉树的存储结构&二叉树的递归遍历(顺序存储结构、链表存储结构、先序中序后序递归遍历)
|
7月前
数据结构——二叉树的遍历【前序、中序、后序】
数据结构——二叉树的遍历【前序、中序、后序】
|
7月前
|
数据格式
树结构练习——排序二叉树的中序遍历
树结构练习——排序二叉树的中序遍历
|
7月前
|
存储 算法 程序员
【算法训练-二叉树 七】【二叉搜索树】验证二叉搜索树、将二叉搜索树转为排序的双向循环链表
【算法训练-二叉树 七】【二叉搜索树】验证二叉搜索树、将二叉搜索树转为排序的双向循环链表
73 0
二叉树——链式存储
✅<1>主页:我的代码爱吃辣 📃<2>知识讲解:数据结构——二叉树 🔥<3>创作者:我的代码爱吃辣 ☂️<4>开发环境:Visual Studio 2022 💬<5>前言:上期讲了二叉树的顺序存储,今天来讲一下二叉树的链式存储。
【数据结构入门】二叉树的遍历(前序、中序、后序、层序)
【数据结构入门】二叉树的遍历(前序、中序、后序、层序)
824 0
|
存储
基于中序有序的二叉搜索树(下)
基于中序有序的二叉搜索树
|
存储
【数据结构二叉树的链式存储讲解及前中后序遍历和层次遍历】
二叉树的链式存储结构是指, 用链表来表示一棵二叉树, 即用链来指示元素的逻辑关系。
322 0
|
存储
LeetCode——1305. 两棵二叉搜索树中的所有元素
LeetCode——1305. 两棵二叉搜索树中的所有元素
55 0
LeetCode——1305. 两棵二叉搜索树中的所有元素