二叉搜索树,就这,就这啊。
二叉搜索树
- 概念
- 插入节点
- 递归插入
- 非递归
- 查找节点
- 递归查找
- 非递归
- 删除节点
- 递归删除
- 非递归删除
什么是二叉搜索树?
二叉搜索树,可知道他是一个二叉树,并且肯定是为了有和搜索相关的地方,那他到底有什么不一样的呢?
github源码地址 : https://github.com/duchenlong/Cpp/tree/master/SearchTree/SearchTree
概念
所谓二叉搜索树,也叫二叉排序树。他或者是一颗空树,或者他一定具有以下的几个性质
- 如果他的左子树不为空,那么左子树的所有节点的值都
小于根节点
的值 - 如果他的右子树不为空,那么右子树的所有节点的值都
大于根节点
的值 - 他的左右子树也分别为二叉搜索树
类似于这样的一个结构,可以看出,每一个根节点都比他的左子树的值大,比他右子树的值小。
对于二叉搜索树而言,一开始的时候,他的根节点是一个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节点
的位置。
所以在插入之前,我们需要先找到这个插入的位置,这时就可以利用二叉搜索树的特性,快速的查找到这个位置,当我们遍历到一个节点
- 如果当前节点的值 比 key 大,说明该数据要插在自己的 左子树
- 如果当前节点的值 比 key 小,说明该数据要插在自己的 右子树
然后直到找到nullptr节点的位置再进行插入操作,或者找到了这个节点(就不需要插入了)。
递归插入
递归插入的时候,我们需要注意,我们所插入的位置一定是一个nullptr节点
,空指针是没有地址的,所以我们递归插入的时候,有两种传参方式
- 参数加上父亲节点的指针,然后将申请的节点和父亲节点连接起来
- 使用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;}
删除节点
删除节点就比较麻烦了,这时候就需要分情况去删除了,有着这样的三种情况:
- 需要删除的节点的左子树是
nullptr
,这个时候我们只需要连接他的右子树和父亲节点就可以了
- 需要删除的节点的右子树是
nullptr
,这个时候我们只需要连接他的左子树和父亲节点
- 需要删除的节点,他的左右子树都不为
nullptr
对于第三种情况,他有着两种删除方式:
- 把删除节点的左子树移动到他右子树的最小的左节点的左子树位置
这样删除,需要面临的一个问题就是,他会使得这棵树的高度变得更高,所以说是不可取的。因为高度变高之后,可能就会失去二叉搜索树原本搜索变快的目的
- 把删除节点右子树的最小的左节点,和删除节点的值进行交换,然后删除这个最小的左节点
递归删除
//递归删除接口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;}
最后,如果我们一开始建立二叉搜索树的时候,给的数字都是有序的数字,那么就会出现一棵树,他只有左子树,或者只有右子树的情况,这个时候就需要我们去平衡二叉树了。
二叉搜索树,就这,就这啊。相关推荐
- LeetCode简单题之二叉搜索树的最小绝对差/最小距离
题目 给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 . 差值是一个正数,其数值等于两值之差的绝对值. 示例 1: 输入:root = [4,2,6,1,3] 输出: ...
- LeetCode简单题之二叉搜索树的范围和
题目 给定二叉搜索树的根结点 root,返回值位于范围 [low, high] 之间的所有结点的值的和. 示例 1: 输入:root = [10,5,15,3,7,null,18], low = 7, ...
- 【剑指Offer】23、二叉搜索树的后序遍历序列
题目描述: 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果.如果是则输出Yes,否则输出No.假设输入的数组的任意两个数字都互不相同. 解题思路: 对于后续遍历序列,序 ...
- 消除左递归实验代码_「leetcode」108. 构造二叉搜索树【递归】【迭代】详解!
构造二叉搜索树,一不小心就平衡了 ❞ 108.将有序数组转换为二叉搜索树 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树. 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树 ...
- 剑指offer:面试题36. 二叉搜索树与双向链表
题目: 二叉搜索树与双向链表 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表.要求不能创建任何新的节点,只能调整树中节点指针的指向. 为了让您更好地理解问题,以下面的二叉搜索树为例: ...
- 剑指offer:面试题33. 二叉搜索树的后序遍历序列
题目:二叉搜索树的后序遍历序列 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果.如果是则返回 true,否则返回 false.假设输入的数组的任意两个数字都互不相同. 参考以下这颗二叉搜 ...
- 二叉树:二叉搜索树实现 逆序数问题
关于逆序数的问题描述如下: 已知数组nums,求新数组count,count[i]代表了在nums[i]右侧且比 nums[i]小的元素个数. 例如: nums = [5, 2, 6, 1], cou ...
- 二叉树:二叉搜索树的编码和解码
二叉搜索树的编码和解码描述: 编码:即将一个二叉搜索树编码,节点数值转换为字符串 解码:即将一个字符串解码,数值转换为对应的二叉搜索树的节点 过程导图如下: 针对性编码实现如下: /*数字转字符串*/ ...
- 二叉树:二叉搜索树的创建和插入
二叉搜索树又名二叉排序树. 大概简略的思维导图如下,方便记忆特性 基本二叉搜索树创建过程如下 /*数据结构如下*/ typedef struct tree {int data;struct tree ...
- 6-12 二叉搜索树的操作集
6-12 二叉搜索树的操作集(30 分) 本题要求实现给定二叉搜索树的5种常用操作. 函数接口定义: BinTree Insert( BinTree BST, ElementType X ); Bin ...
最新文章
- python神经网络风格_[Deep-Learning-with-Python]使用LSTM生成尼采风格文章
- 解决ASP网页乱码的问题
- slim.flatten——将输入扁平化但保留batch_size,假设第一维是batch
- Vijos 1334 ---- NASA的食物计划(01背包强化)
- python提取表里数据_使用python解析/提取表数据
- python转义字符表
- protel99SE - 多张原理图生成一张总网表的方法
- 计算机中安装音乐软件是一种,电脑必装的八款软件,你装了吗?
- Android中实现微信分享的功能
- 【BZOJ4484】【JSOI2015】最小表示(拓扑排序,bitset)
- 网络原理考点之无线网络应用层协议
- Pandoc安装与使用总结
- 企业级网络性能优化 课内7 多臂单臂路由
- 【自动化测试】自动化测试框架那些事儿
- createjs打飞机
- 一个一站式流式处理云平台解决方案
- Verilog学习之数据大小端转换设计
- 2021Pycharm学生免费使用
- Golang通用后台权限管理系统
- 脉冲编码调制pcm matlab,基于MATLAB的脉冲编码调制(PCM)