二叉搜索树

  • 概念
  • 插入节点
    • 递归插入
    • 非递归
  • 查找节点
    • 递归查找
    • 非递归
  • 删除节点
    • 递归删除
  • 非递归删除

什么是二叉搜索树?

二叉搜索树,可知道他是一个二叉树,并且肯定是为了有和搜索相关的地方,那他到底有什么不一样的呢?

github源码地址 : https://github.com/duchenlong/Cpp/tree/master/SearchTree/SearchTree

概念

所谓二叉搜索树,也叫二叉排序树。他或者是一颗空树,或者他一定具有以下的几个性质

  1. 如果他的左子树不为空,那么左子树的所有节点的值都小于根节点的值
  2. 如果他的右子树不为空,那么右子树的所有节点的值都大于根节点的值
  3. 他的左右子树也分别为二叉搜索树


类似于这样的一个结构,可以看出,每一个根节点都比他的左子树的值大,比他右子树的值小。

对于二叉搜索树而言,一开始的时候,他的根节点是一个nullptr,所以当我们把一个数组中的元素按照不同的顺序进行插入的时候,所得到的二叉搜索树的结构可能会不同。

因为我们插入的第一个节点的值就是树的根节点的值,以后所有的数据都是围绕这个根节点进行插入的。所以二叉搜索树不唯一

这也是一颗二叉搜索树

所以说,对于二叉搜索树的描述,我们可以用这样的一个结构体

//存储树中节点的结构体
template<class T>
struct TreeNode
{T _key;TreeNode<T>* _left;TreeNode<T>* _right;TreeNode(const T key = 0):_key(key), _left(nullptr), _right(nullptr){}
};

另外,我们可以很直观的看到,对于这棵二叉搜索树,将他按照中序的方式进行遍历,那么他的遍历的结果就是一个有序的数组。

 //中序遍历接口void _Inorder(TreeNode* root){if (!root) return;_Inorder(root->_left);cout << root->_key << ' ';_Inorder(root->_right);}

接下来就是一些增删查的接口了

插入节点

在插入元素之前,我们需要明确,首先二叉搜索树是不能存在相同元素的,其次,我么每次所插入的位置一定是一个nullptr节点的位置。

所以在插入之前,我们需要先找到这个插入的位置,这时就可以利用二叉搜索树的特性,快速的查找到这个位置,当我们遍历到一个节点

  1. 如果当前节点的值 比 key 大,说明该数据要插在自己的 左子树
  2. 如果当前节点的值 比 key 小,说明该数据要插在自己的 右子树

然后直到找到nullptr节点的位置再进行插入操作,或者找到了这个节点(就不需要插入了)。

递归插入

递归插入的时候,我们需要注意,我们所插入的位置一定是一个nullptr节点,空指针是没有地址的,所以我们递归插入的时候,有两种传参方式

  1. 参数加上父亲节点的指针,然后将申请的节点和父亲节点连接起来
  2. 使用C++的引用,这样我们直接给nullptr节点申请空间的时候,就可以直接和他的父亲节点连接
 //递归插入bool InsertR(const T key){return _InsertR(root, key);}//递归插入接口bool _InsertR(TreeNode*& root, const T key){//根节点为 nullptr ,所以说待插入的位置就是根节点的位置if (!root){root = new TreeNode(key);return true;}//如果根节点的值和key相同,就不需要插入if (root->_key == key) return true;//当前位置不是插入位置,判断需要递归插入左右子树哪里bool ret = root->_key > key ? _InsertR(root->_left, key) : _InsertR(root->_right, key);return ret;}

非递归

 //迭代插入bool Insert(const T key){TreeNode* cur = root;TreeNode* parent = nullptr;//找到需要插入的位置,如果这个位置是真,说明还不是待插入的地方while (cur){//先排除等于的情况if (cur->_key == key) return true;parent = cur;//如果当前节点的值 比 key 大,说明该数据要插在自己的 左子树//如果当前节点的值 比 key 小,说明该数据要插在自己的 右子树cur = cur->_key > key ? cur->_left : cur->_right;}cur = new TreeNode(key);//注意当前cur节点为nullptr,他是没有地址的,我们需要把新申请的节点和他的父亲节点手动连接parent == nullptr ? root = cur : (parent->_key > key ? parent->_left = cur : parent->_right = cur);return true;}

查找节点

对于查找,其实也是充分利用了二叉搜索树的特点,左子树都是比自己小的,右子树都是比自己大的

递归查找

 //递归查找TreeNode* FindR(const T key){return _FindR(root, key);}//查找调用的接口TreeNode* _FindR(TreeNode*& root, const T key){//根节点为空,或者根节点为Key,就直接返回root了if (!root || root->_key == key) return root;//没找到,就到子树的逻辑中找TreeNode* ret = root->_key > key ? _FindR(root->_left, key) : _FindR(root->_right, key);return ret;}

非递归

 //迭代查找TreeNode* Find(const T key){TreeNode* cur = root;while (cur){if (cur->_key == key) return cur;cur = cur->_key > key ? cur->_left : cur->_right;}//这里的时候,cur为nullptr ,说明没有找到,返回nullptrreturn nullptr;}

删除节点

删除节点就比较麻烦了,这时候就需要分情况去删除了,有着这样的三种情况:

  1. 需要删除的节点的左子树是nullptr,这个时候我们只需要连接他的右子树和父亲节点就可以了
  2. 需要删除的节点的右子树是nullptr,这个时候我们只需要连接他的左子树和父亲节点
  3. 需要删除的节点,他的左右子树都不为nullptr

对于第三种情况,他有着两种删除方式:

  1. 把删除节点的左子树移动到他右子树的最小的左节点的左子树位置


这样删除,需要面临的一个问题就是,他会使得这棵树的高度变得更高,所以说是不可取的。因为高度变高之后,可能就会失去二叉搜索树原本搜索变快的目的

  1. 把删除节点右子树的最小的左节点,和删除节点的值进行交换,然后删除这个最小的左节点

递归删除

//递归删除接口bool _EarseR(TreeNode*& root,const T key){//根节点为空,说明没有key的节点if (!root) return false;if (root->_key != key)return root->_key > key ? _EarseR(root->_left, key) : _EarseR(root->_right, key);//这里就是root->_key == key 的逻辑//此时就需要删除 root 节点TreeNode* del = root;if (!root->_right)   //需要删除的位置的右节点为空,根节点 = 他的左孩子root = root->_left;else if (!root->_left) //需要删除位置的左节点为空,根节点 = 他的右孩子root = root->_right;else  //需要删除节点左右孩子均不为空{TreeNode* cur = root->_right;//找到最后一个左孩子while (cur->_left){cur = cur->_left;}//此时需要把cur放到根节点的位置,然后删除cur节点root->_key = cur->_key;//这里可以变成在这个右子树中,删除cur节点return _EarseR(root->_right, cur->_key);/*//这样会让整个树变高的可能//将该位置的左孩子,插入到第一个右孩子的最后一个左孩子的位置TreeNode* left = root->_left;TreeNode* right = root->_right;//找到第一个右孩子的左孩子中,找到最后的空节点TreeNode* cur = right;while (cur)cur = cur->_left;//连接左孩子cur = left;root = right;*/}delete del;return true;}

非递归删除

对于非递归的情况,这个时候我们不能使用引用类型,因为引用会使得原本根节点的位置发生变化。

所以我们需要设置一个变量,这个变量用来记录父亲节点的位置,当这个变量为nullptr时,表示删除的节点是根节点

 //迭代删除bool Earse(const T key){//先查找该节点,同时保留父亲节点的位置TreeNode* parent = nullptr;TreeNode* cur = root;while (cur){if (cur->_key == key) break;parent = cur;cur = cur->_key > key ? cur->_left : cur->_right;}//如果没有这个节点,就不需要删除if (!cur) return false;TreeNode* del = cur;//此时需要删除cur节点,注意要删除的位置是根节点的情况if (!cur->_left){if (!parent)root = cur->_right;else if (cur == parent->_left) //要删除的节点是左子树parent->_left = cur->_right;else if (cur == parent->_right)    //要删除的节点是右子树parent->_right = cur->_right;}else if (!cur->_right){if (!parent)root = cur->_left;else if (cur == parent->_left)//要删除的节点是左子树parent->_left = cur->_left;else if (cur == parent->_right)//要删除的节点是右子树parent->_right = cur->_left;}else{TreeNode* minParent = cur;TreeNode* minLeft = cur->_right;//找到最后一个左孩子while (minLeft->_left){minParent = minLeft;minLeft = minLeft->_left;}//此时需要把minLeft放到cur节点的位置,然后删除minLeft节点cur->_key = minLeft->_key;//删除minLeft节点,但是防止minLeft的右孩子存在if (minParent->_right == minLeft) //说明第一个右孩子没有左孩子minParent->_right = minLeft->_right;else //删除这个左孩子,他的位置就让 左孩子的右孩子顶替minParent->_left = minLeft->_right;del = minLeft;//此时的minLeft变成了要删除的节点}delete del;return true;}

最后,如果我们一开始建立二叉搜索树的时候,给的数字都是有序的数字,那么就会出现一棵树,他只有左子树,或者只有右子树的情况,这个时候就需要我们去平衡二叉树了。

二叉搜索树,就这,就这啊。相关推荐

  1. LeetCode简单题之二叉搜索树的最小绝对差/最小距离

    题目 给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 . 差值是一个正数,其数值等于两值之差的绝对值. 示例 1: 输入:root = [4,2,6,1,3] 输出: ...

  2. LeetCode简单题之二叉搜索树的范围和

    题目 给定二叉搜索树的根结点 root,返回值位于范围 [low, high] 之间的所有结点的值的和. 示例 1: 输入:root = [10,5,15,3,7,null,18], low = 7, ...

  3. 【剑指Offer】23、二叉搜索树的后序遍历序列

      题目描述:   输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同.   解题思路:   对于后续遍历序列,序 ...

  4. 消除左递归实验代码_「leetcode」108. 构造二叉搜索树【递归】【迭代】详解!

    构造二叉搜索树,一不小心就平衡了 ❞ 108.将有序数组转换为二叉搜索树 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树. 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树 ...

  5. 剑指offer:面试题36. 二叉搜索树与双向链表

    题目: 二叉搜索树与双向链表 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表.要求不能创建任何新的节点,只能调整树中节点指针的指向. 为了让您更好地理解问题,以下面的二叉搜索树为例: ...

  6. 剑指offer:面试题33. 二叉搜索树的后序遍历序列

    题目:二叉搜索树的后序遍历序列 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果.如果是则返回 true,否则返回 false.假设输入的数组的任意两个数字都互不相同. 参考以下这颗二叉搜 ...

  7. 二叉树:二叉搜索树实现 逆序数问题

    关于逆序数的问题描述如下: 已知数组nums,求新数组count,count[i]代表了在nums[i]右侧且比 nums[i]小的元素个数. 例如: nums = [5, 2, 6, 1], cou ...

  8. 二叉树:二叉搜索树的编码和解码

    二叉搜索树的编码和解码描述: 编码:即将一个二叉搜索树编码,节点数值转换为字符串 解码:即将一个字符串解码,数值转换为对应的二叉搜索树的节点 过程导图如下: 针对性编码实现如下: /*数字转字符串*/ ...

  9. 二叉树:二叉搜索树的创建和插入

    二叉搜索树又名二叉排序树. 大概简略的思维导图如下,方便记忆特性 基本二叉搜索树创建过程如下 /*数据结构如下*/ typedef struct tree {int data;struct tree ...

  10. 6-12 二叉搜索树的操作集

    6-12 二叉搜索树的操作集(30 分) 本题要求实现给定二叉搜索树的5种常用操作. 函数接口定义: BinTree Insert( BinTree BST, ElementType X ); Bin ...

最新文章

  1. python神经网络风格_[Deep-Learning-with-Python]使用LSTM生成尼采风格文章
  2. 解决ASP网页乱码的问题
  3. slim.flatten——将输入扁平化但保留batch_size,假设第一维是batch
  4. Vijos 1334 ---- NASA的食物计划(01背包强化)
  5. python提取表里数据_使用python解析/提取表数据
  6. python转义字符表
  7. protel99SE - 多张原理图生成一张总网表的方法
  8. 计算机中安装音乐软件是一种,电脑必装的八款软件,你装了吗?
  9. Android中实现微信分享的功能
  10. 【BZOJ4484】【JSOI2015】最小表示(拓扑排序,bitset)
  11. 网络原理考点之无线网络应用层协议
  12. Pandoc安装与使用总结
  13. 企业级网络性能优化 课内7 多臂单臂路由
  14. 【自动化测试】自动化测试框架那些事儿
  15. createjs打飞机
  16. 一个一站式流式处理云平台解决方案
  17. Verilog学习之数据大小端转换设计
  18. 2021Pycharm学生免费使用
  19. Golang通用后台权限管理系统
  20. 脉冲编码调制pcm matlab,基于MATLAB的脉冲编码调制(PCM)

热门文章

  1. MySQL实验7存储过程_mySQL 教程 第7章 存储过程和函数
  2. vue页面加载时闪现_Vue 闪现解决
  3. 小猫钓鱼纸牌游戏java_java实现纸牌游戏-小猫钓鱼算法
  4. 全长扩增子:是时候展示真正的技术了
  5. JavaScript 身份证校验正则表达式
  6. 20210610 线程数不断飙升问题定位
  7. 为什么年轻人会活的越来越难?
  8. 我的PCB设计经验——奥研电子整理
  9. 一个简易Android购物App的开发
  10. windows bat 批处理脚本编写指南