二叉树的进阶
前言:
关于二叉树的基础知识,小生这里就不在一一一赘述了,对前面二叉树的基础知识有遗忘的铁子
们,可以康康前期咱的博客。
链接在此: 数据结构之二叉树 的精讲
目录:
一:二叉搜索树的定义
1.1 二叉搜索树的定义
首先二叉搜索树是一个递归的定义:左子树的所有节点都小于根节点;右子树 的所有节点都大于
根节点。
其中左子树,右子树又满足二叉搜索树 的性质
对于一个二叉搜索树而言,可以为空
对于一个二叉搜索树又名为二叉排序树或者二叉查找树。
对二叉搜索树进行中序遍历得到的是一个升序的序列。
中序遍历:先访问左孩子,再访问根节点,最后访问右孩子节点(递归的访问)
对于上面二叉搜索树进行输出:1 2 3 4 5 6 7
注意:一般而言,是不支持存储重复的节点。
二:二叉搜索树的实现
2.1 二叉搜索树的建立
其实对一棵二叉搜索树进行创建和对二叉树进行创建的过程基本是大同小异,只不过受到一些条件
的限制。
分析:
在插入指定的节点之后需要满足:左子树的节点小于根节点,右子树的节点大于根节点。
前期的准备工作:
x:当前需要插入的节点
先找到一个合适的位置;在进行指针的链接
这里的 关键就是如何记录插入当期节点的父节点???
使用递归进行代码编写:在传参数的时候需要传当前节点的引用,因为当这是一颗空树 的时候,
在函数 的内部实现对树的修改,传引用直接解决了这一问题。
对应代码:
2.2 二叉搜索树的输出
其实就是一个中序的遍历;
这里借助递归实现中序遍历:
之所嵌套一个函数实现中序的输出,是因为在函数外不能直接取到二叉搜索树的根节点,所以一
般嵌套一个子函数进行函数的调用
2.3 二叉搜索树的查找
依然是借助二叉搜索树的性质:左子树的所有节点小于根节点;右子树的所有 节点大于根节点
递归实现:
2.4二叉搜索树的删除
对于删除操作,没有前面那么简单了,难度就上来了~~~
1)删除叶子结点
2)删除只有一个孩子的节点
删除的是一个右孩子节点:
删除的是一个左孩子节节点:
通过画图发现对于删除叶节点或者删除只有一个孩子的节点,这两类情况可以归为一种问题
因为删除叶节点后,关键就是在于对应的父节点孩子到底是指向删除节点的左孩子还是右孩子的问
题,此时不管指向左孩子也好,还是指向右孩子也罢,对于叶节点的孩子都是为空的,所以二者可
以归为一种情况。
总体的删除框架:
1)先判断删除节点的孩子是否为空:目的避免对一个既有左孩子又有右孩子的节点进行删除
2)在判断删除的节点是否为根节点
3)最后进行判断删除的节点是父节点的左孩子还是右孩子:目的便于父节点与删除节点的孩子进
行链接
对于判断删除节点是否为根节点还是删除节点是左孩子这一逻辑是不能进行颠倒的
对应的代码:
//1)先对删除节点的孩子情况进行判断便于后期父节点孩子与删除节点的孩子进行链接if (cur->_left == nullptr){//2)必须先判断删除的节点是否为根节点:只有一个节点的二叉搜索树同时也是一个叶节点if (cur == _root){_root = _root->_right;delete cur;return true;}else{//3)再进行判断删除节点是左孩子还是右孩子if (cur == parent->_left){parent->_left = cur->_right;delete cur;return true;}else{parent->_right = cur->_right;delete cur;return true;}}}else if (cur->_right == nullptr){//这个整体的框架和上面删除一样//是否删除根节点if (cur == _root){_root = _root->_left;delete _root;return true;}else{// 删除的节点是左孩子还是右孩子if (parent->_left == cur){parent->_left = cur->_left;delete cur;return true;}else{parent->_right = cur->_left;delete cur;return true;}}}
3)删除有左右孩子的节点
对应的代码:
//1)先找到右子树的最小节点:注意最小节点拷贝一定就是在左面:也可能最小节点是删除节点的右孩子TreeNode* subLeft = cur->_right;while (subLeft->_left){parent = subLeft;subLeft = subLeft->_left;}//找到了最小节点但是可能存在循环不进入的情况,所有parent可能为空,在初始化的时候parent= cur即可即可解决此问题//2)对_key 进行交换,目的转化为对最小节点的删除swap(cur->_key, subLeft->_key);//swap(key, subLeft->_key);//err// 3)判断subLeft是左孩子还是右孩子if (subLeft == parent->_left){parent->_left = subLeft->_right;delete subLeft;return true;}else{//parent->_right = subLeft->_left;//err这样写,当subLeft是叶节点的时候OK,但是当这是一个右叉树的时候,只能指向subLeft 的右孩子parent->_right = subLeft->_right;delete subLeft;return true;}}
对于容器一般进行的操作无非就是常见的“增删查改”。但是对于二叉搜索树进行修改操作后不一定
能保证修改之后还是一棵完整的二叉搜索树。
二叉树的常用函数:
拷贝构造函数:使用先序遍历进行创建
对于拷贝构造函数的递归调用见下:
这里有个小问题:对于参数为什么不能使用 引用传参,也就是 TreeNode* & root
本质问题是:权限放大的问题
此时实参被const 进行修饰了,当在调用Copy(TreeNode* & root) 的时候,编译器会生成一个临时
对象,这个临时对象无法赋值给形参,因为权限被放大了。
注意:在传参的时候参数不是直接就传给形参了,而是编译器借助另一个临时变量,把实参赋值给
一个临时变量,再通过临时变量赋值给给形参,但是这样的效率比较慢。所以C++提出使用引用这
个概念,来解决此问题。
对于引用相关知识点,小生在这里就不在多多叨叨了,链接在此:引用相关介绍
赋值重载函数:
析构函数:
使用后续遍历进行销毁
接下来“硬菜”来了,使用递归编写对节点的删除代码:
bool _EraseR(TreeNode* root, const k& key){//1:先对删除 的节点进行查找if (root->_key > key)_Erase(root->_left, key);else if (root->_key < key)_Erase(root->_right, key);else{//准备删除//2)判断删除节点左和右孩子是否为空if (root->_left == nullptr){//关键就是如何取到删除节点的父节点//参数:传引用TreeNode* del = root;root = root->_right;delete del;return true;}else if (root->_right == nullptr){TreeNode* del = root;root = root->_left;delete del;return true;}else{//删除节点的左右孩子都不为空//1:找右子树最小节点TreeNode* subLeft = root->_right;while (subLeft->_left){subLeft = subLeft->_left;}swap(root->_key, subLeft->_key);//转化为对subLeft所在的子树进行删除subLeft这个节点_Erase(root, key);return true;}}return false;}
总结:
以上就是关于二叉搜索树的基本操作,二叉搜索树是一种重要的数据结构,对于后面的AVL树,红
黑树,B树,B+树的学习,起着重要的作用。对于一个有序的数据的查询和输出,使用二叉搜索树
进行操作,效率是非常高的。
关于满二叉树,完全二叉树,二叉搜索树对比总结:
满二叉树:除了根节点和叶节点之外,所有的节点都有2个孩子节点,在高度为 h 的一个满二叉树
里面,总的节点个数是 2^h -1
完全二叉树:是一种特殊的二叉树,假设高度为h,前 h-1 层符合满二叉树的特点;最后一次节点
可能满了也可能不满,但是一定是自左向右连贯的。
注意:满二叉树一定是完全二叉树,但是完全二叉树不一定就是满二叉树。
二叉搜索树:所有的左子树的键值都小于根节点,所有的右子树的键值都大于根节点(递归定
义);对二叉搜索树进行中序遍历输出的是一个升序的序列。