目录
引言
1.二叉搜索树的模拟实现
1.1 链式二叉树的定义
1.2 二叉搜索树的模拟实现
1.2.1 二叉搜索树的结点类
1.2.2 二叉搜索树类的构造与中序遍历实现
1.2.3 增
1.非递归实现
2.非递归实现
1.2.4 查
1.非递归实现
2.递归实现
1.2.5 删
1.非递归实现
(1)情况分析
(2)解决方案
(3)领养代码实现
(4)替代法代码实现
2.递归实现
1.2.6 整体代码展示
2.二叉搜索树的应用
2.1 key模型
2.2 key-value(kv)模型
3.二叉搜索树的相关oj题
1. 二叉树创建字符串
2. 二叉树的分层遍历1
3. 二叉树的分层遍历2
4. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先
5. 二叉树搜索树转换成排序双向链表
6. 根据一棵树的前序遍历与中序遍历构造二叉树
7. 根据一棵树的中序遍历与后序遍历构造二叉树
引言
在之前文章中,我们用C语言实现了链式二叉树,在该节,我们将会通过C++的方式,对二叉树进
一步研究,尝试手动模拟实现二叉搜索树,为接下来学习map,set打下相应的基础
1.二叉搜索树的模拟实现
1.1 链式二叉树的定义
在链式二叉树的一节中,我们曾经提到过
由于二叉树的形状是不确定的,甚至可以全部倾向一边,构成我们熟悉的单链表.
因此,从某种意义上来说,二叉树的增删查改并没有意义,除非有着特殊结构的限定
所以,这节主要研究的就是二叉树中一种特殊的结构——搜索二叉树(如图所示)
搜索二叉树的特点:
1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
3. 它的左、右子树也分别为二叉搜索树
1.2 二叉搜索树的模拟实现
1.2.1 二叉搜索树的结点类
struct BSTreeNode
{BSTreeNode <K>* _left;BSTreeNode <K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){}
};
构造函数,直接将结点左右指针都赋为空,同时_key置为用户给的key值
1.2.2 二叉搜索树类的构造与中序遍历实现
二叉搜索树类的成员就一个——根
为了接下来代码书写简便,和前面模拟实现一样,对二叉搜索树类型重命名
typedef BSTreeNode<K> Node;
二叉搜索树其实还有一个非常有趣的特征,中序遍历二叉搜索树,得到的会是一个有序的序列
因此,我们可以通过在类里面实现中序遍历,来迅速简单验证我们构造的二叉搜索树是否成功
调用中序遍历Inorder,需要我们传根,但是根是我们类里面的私有成员
在类外部,是无法直接访问的,除非我们在类里面加一个Getroot的成员函数
不过这样就会显得代码有些冗余,所以我们会换一种做法,通常是将中序遍历进行嵌套实现
这样用户调用的时候,不需要传根,同时在类里面,我们也可以直接访问根这个私有成员
//中序遍历void Inorder(){_Inorder(_root);cout << endl;}void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->_left);cout << root->_key << " ";_Inorder(root->_right);}
PS:
这里不能采用给缺省值来解决问题
void _Inorder(Node* root = _root) ❌
1.缺省值必须是全局变量或者常量,而这里的_root显然不是
2.函数成员变量的访问,需要this指针,它是一个临时形参,只能在函数内部访问,现在连函数内部都还没进去,怎么可以通过this指针来访问函数成员呢?
1.2.3 增
1.非递归实现
我们第一个实现的是插入函数,这样我们就可以先构造出我们的二叉搜索树,并简单用中序遍历,
验证实现是否成功,这样再进行后续的模拟实现
二叉搜索树的插入(增)
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点
树为空,这没有什么好解释的,new一个结点,对应结点指针指向_root即可
但假如树不为空,就必须满足二叉搜索树的性质,先找到结点的正确位置,再进行插入
找到结点位置没有什么好解释的,就按照新节点的val值和现在当前节点的val作比较,如果比它
大,则向右移动插入,否则,就向左移动插入
同时为了方便插入,我们还需要随时记录插入结点的父节点位置指针
比如下面这张图,要插入16,我们移动cur的同时,还需要记录它的parent,这样当cur为空,即找
到对应的正确位置,就及时将parent指向它
PS:cur可能是parent的左孩子也可能是右孩子,所以在调整指向的时候,还需要进一步判断
bool insert(const K& key){//如果是第一个元素,直接就将它作为二叉树的根if (_root == nullptr){_root = new Node(key);return true;}//如果不是就考虑插入元素Node* parent = nullptr;Node* cur = _root;while (cur){//如果当前结点的值小于Key,往右移动if (cur->_key < key){ parent = cur;cur = cur-> _right;}//如果当前结点的值大于Key,往左移动else if (cur->_key > key){parent = cur;cur = cur->_left;}//出现相同元素,直接报错else{return false;}}//找到对应正确位置,new一个节点,并且将parent指向它cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}
2.非递归实现
前面我们已经讲过,如果要在类外部访问私有成员变量_root,那就需要增添Getroot成员函数
所以一般在类内部实现递归函数,我们都用嵌套实现
这里有一个技巧非常绝妙,我们注意到_InsertR函数参数中,root用的是引用传参
所以找到正确的位置,传给root,直接就是parent->left或者parent->right的别名
充分利用了函数栈帧和引用相关知识,设计得很有意思
//二叉树插入(递归版本)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;}}
1.2.4 查
二叉搜索树的查找
a. 从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找
b. 最多查找高度次,走到到空,还没找到,这个值不存在。
总体思路和插入非常类似,相比而言,会比插入还要简单,相当于插入的节选代码,稍作修改,两
者甚至可以实现赋用
1.非递归实现
//二叉树查找bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}elsereturn true;}return false;}
2.递归实现
//二叉树查找(递归版本)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);}}
1.2.5 删
1.非递归实现
(1)情况分析
删除可以说是二叉搜索树模拟实现的核心,而且难度也最高
我们需要一步步进行分析
首先对于被删除的节点,我们需要考虑它的child,节点被删除,那child指向肯定就要作出调整
比如说删除16,那就可以直接删除即可
但是删除15,或者说删除18,就会是另外不同的情况,不可能删除15,然后把17和16两个结点都
弄丢的,所以我们需要先把不同的情况列出来,然后根据每种情况,提出不同的解决方案
总共有四种情况
列写思路也很简单,这个节点有没有孩子,无孩子,就像图中的16;有孩子的话有几个孩子?有一
个孩子,和有两个孩子是不同的情况
无孩子
a. 要删除的结点无孩子结点
有孩子(有几个孩子?)
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
但事实上,我们可以把a情况,放到b情况或者c情况中,用统一方法解决,所以总共可以算三种情
况
(2)解决方案
对于第一种情况,直接删除即可,没有孩子需要处理,比如说16这个节点
对于第二,第三种情况,我们采取托孤的方式,让爷爷来带孙子,毕竟父亲被删除了,那爷爷自然
就可以空出一只手,我们让被删结点的爸爸,也就是爷爷,来照顾孙子,就可以解决只有左孩子和
右孩子的情况
比如15这个节点,我们把它删除,那它的父节点18自然会空出一只手,来领养17这个儿子
PS:
第一种情况,可以归到第二,三种情况处理,删除16,实际上就是让17领养孙子,孙子为空指针,没有差别,可以直接让17领养空结点
对于第三种情况,我们采取替代的方法,寻找被删除节点左树的最大节点或者右树的最小节点
为什么要用左树的最大节点或者右树的最小节点来替代呢?
1.因为我们要保证替代后,它仍然是一棵搜索二叉树
左树的最大节点,或者右树的最小节点,都可以满足替代后,根大于左子树的所有结点,同时小于右子树的所有结点
2.替代后,删除原结点,可以转换为我们熟悉的前两种情况
比如说删除12,我们用它的左子树的最大结点来替代(其实就是把12这个值改成9)
然后把原来9这个结点直接释放,就实现了删除12结点的操作
9由于是从左子树里面挑出来的,所以比12右子树所有结点都要小
但又是左子树里面最大的,所以完全可以实现替代12之后,保持是一棵搜索二叉树
(类似我们也可以选择15——右树的最小结点来替代)
(3)领养代码实现
现在讨论完不同情况后,我们就可以开始着手实现代码,在这期间,同样有很多需要注意的细节
首先,我们还是要找到这个要删除的结点,同样的,我们也要记录它的父亲
我们把第一种情况也放在第二种一同解决
但这里还需要注意两个点
第一,假如被删结点,没有父节点呢?也就是没有爷爷,此时就无法实现托孤了,对空指针解引用,会造成程序的崩溃
第二,被删结点的父节点不是随便领养的,还需要判断被删结点原来是父节点的右孩子还是左孩子,只有空出来的手才可以领养孩子
//如果该结点没有右孩子
if (cur->_right == nullptr)
{ //判断cur是不是根,如果是根,则没有parentif (cur == _root){_root = cur->_left;}else{//并且该结点是父节点的右孩子,则让parent的右指针领养它的左孩子if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;
}//如果该结点没有左孩子
else if (cur->_left == nullptr)
{//并且该结点是父节点的右孩子,则让parent的右指针领养它的右孩子//父亲有可能为空,因为根没有parentif (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;
}
(4)替代法代码实现
接下来,我们需要处理最复杂的第三种情况,也就是用替代法,来删除有两个孩子的结点
这里采用右树最小节点的方式来替代,把右树最小节点,记为minRight
它的父节点指针,记作pminRight
只要左子树不为空,我们就一直往左移动,毕竟一棵搜索二叉树的最小节点,一定在最左边
找到后,将它的值和被删结点互换即可
但是还是有两点需要注意
第一,右树的最小节点一定没有左孩子,否则就不是最小节点
这不意味着右树最小节点一定没有右孩子,所以删除的时候,同样需要考虑领养问题
比如说删除12,则15就是右子树的最小节点,删除15后,还需要考虑18和17的领养问题
第二,pminRight不能初始值赋为空,假如没有进循环找minRight,则pminRight就始终为空,则后面领养的时候,空指针解引用,会导致程序崩溃
//该结点有两个孩子,找左子树的最大值,或者右子树的最小值
else
{ //pMinRight不能赋为空,否则没有进循环,空指针解引用报错Node* pMinRight = cur;Node* MinRight = cur->_right;//只要左子树不为空,就一直往左移动,找右树最小节点while (MinRight->_left){ pMinRight = MinRight;MinRight = MinRight->_left;}//直接赋值cur->_key = MinRight->_key;//删除相应结点,需要考虑领养问题//右子树的最小节点,一定没有左孩子,但无法确定是否有右孩子if (pMinRight->_right == MinRight){pMinRight->_right = MinRight->_right;}else{pMinRight->_left = MinRight->_right;}delete MinRight;
}
将上述全部代码结合起来,就是整个删除的非递归代码
//二叉树删除
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->_right == nullptr){ //判断cur是不是根,如果是根,则没有parentif (cur == _root){_root = cur->_left;}else{//并且该结点是父节点的右孩子,则让parent的右指针领养它的左孩子if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;}//如果该结点没有左孩子else if (cur->_left == nullptr){//并且该结点是父节点的右孩子,则让parent的右指针领养它的右孩子//父亲有可能为空,因为根没有parentif (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;}//该结点有两个孩子,找左子树的最大值,或者右子树的最小值else{ //pMinRight不能赋为空,否则没有进循环,空指针解引用报错Node* pMinRight = cur;Node* MinRight = cur->_right;//只要左子树不为空,就一直往左移动while (MinRight->_left){ pMinRight = MinRight;MinRight = MinRight->_left;}//直接赋值cur->_key = MinRight->_key;//删除相应结点,需要考虑领养问题//右子树的最小节点,一定没有左孩子,但无法确定是否有右孩子if (pMinRight->_right == MinRight){pMinRight->_right = MinRight->_right;}else{pMinRight->_left = MinRight->_right;}delete MinRight;}return true;}}return false;
}
2.递归实现
递归的总体思路,和非递归其实是一致的,没有什么区别
下面代码采取的是找出左树最大节点的方式,来实现替代法
不过递归如果要用引用传值来实现,也不是一件简单的事情
在替代法实现删除的时候,假如只有一个孩子,用引用传值来实现递归,和上面其实是一样的
但是如果是替代法,引用传值起到的作用其实不大
还有很关键的一点,由于是层层函数栈帧递归,我们要采用swap,而不是直接赋值
//二叉树删除(递归版本)
bool EraseR(const K& key)
{return _EraseR(_root, key);
}bool _EraseR(Node*& root, const K& key)
{ //假如递归到空节点,说明删除节点不存在,此时return falseif (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;}
}
1.2.6 整体代码展示
#pragma once
template <class K>
struct BSTreeNode
{BSTreeNode <K>* _left;BSTreeNode <K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){}
};
template <class K>
class BSTree
{typedef BSTreeNode<K> Node;
public: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);}//二叉树插入bool insert(const K& key){//如果是第一个元素,直接就将它作为二叉树的根if (_root == nullptr){_root = new Node(key);return true;}//如果不是就考虑插入元素Node* parent = nullptr;Node* cur = _root;while (cur){//如果当前结点的值小于Key,往右移动if (cur->_key < key){ parent = cur;cur = cur-> _right;}//如果当前结点的值大于Key,往左移动else if (cur->_key > key){parent = cur;cur = cur->_left;}//出现相同元素,直接报错else{return false;}}cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}//二叉树查找bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}elsereturn 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->_right == nullptr){ //判断cur是不是根,如果是根,则没有parentif (cur == _root){_root = cur->_left;}else{//并且该结点是父节点的右孩子,则让parent的右指针领养它的左孩子if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;}//如果该结点没有左孩子else if (cur->_left == nullptr){//并且该结点是父节点的右孩子,则让parent的右指针领养它的右孩子//父亲有可能为空,因为根没有parentif (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;}//该结点有两个孩子,找左子树的最大值,或者右子树的最小值else{ //pMinRight不能赋为空,否则没有进循环,空指针解引用报错Node* pMinRight = cur;Node* MinRight = cur->_right;//只要左子树不为空,就一直往左移动while (MinRight->_left){ pMinRight = MinRight;MinRight = MinRight->_left;}//直接赋值cur->_key = MinRight->_key;//删除相应结点,需要考虑领养问题//右子树的最小节点,一定没有左孩子,但无法确定是否有右孩子if (pMinRight->_right == MinRight){pMinRight->_right = MinRight->_right;}else{pMinRight->_left = MinRight->_right;}delete MinRight;}return true;}}return false;}//二叉树查找(递归版本)bool FindR(const K& key){return _FindR(_root, key);}//二叉树插入(递归版本)bool InsertR(const K& key){return _InsertR(_root, key);}//二叉树删除(递归版本)bool EraseR(const K& key){return _EraseR(_root, key);}//中序遍历void Inorder(){_Inorder(_root);cout << endl;}
protected:Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}void Destroy(Node*& root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}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(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(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;}}void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->_left);cout << root->_key << " ";_Inorder(root->_right);}
private:Node* _root = nullptr;
};
2.二叉搜索树的应用
2.1 key模型
K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值
K模型在生活中其实很常见,比如门禁系统,车库系统识别车牌,检查一篇文章中单词拼写是否正
确都可能采用K模型
拿检查一篇文章中单词拼写是否正确这个例子来说,我们将词库中所有单词插入到一棵二叉搜索树
中,然后将文章中每一个单词,和这颗二叉搜索树进行比对,由于二叉搜索树的特性,能够可以迅
速确定该单词是否在这棵树里面,如果没有找到,就意味着单词拼写错误
2.2 key-value(kv)模型
每一个关键码key,都有与之对应的值Value,即键值对。该种方式在现实生活中非常常见
常见的应用,比如说中英文互译字典,或者通过电话号码+验证码来查询考试成绩
都是键-值互换的典型应用
想要实现key_value的二叉搜索树也不是难事,只要在节点中多加入一个参数value即可
其它都不需要改变,建树,找树的整个过程,依旧只和key相关
template<class K, class V>struct BSTreeNode{BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;K _key;V _value;BSTreeNode(const K& key, const V& value):_left(nullptr), _right(nullptr), _key(key), _value(value){}};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;public:bool Insert(const K& key, const V& value){if (_root == nullptr){_root = new Node(key, value);return true;}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{return false;}}cur = new Node(key, value);// 链接if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return cur;}}return nullptr;}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{// 删除// 1、左为空if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;} // 2、右为空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* 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);cout << endl;}protected:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}private:Node* _root = nullptr;};
}
void TestBSTree5()
{string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果",\"苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉", "梨"};key_value::BSTree<string, int> countTree;for (auto str : arr){//key_value::BSTreeNode<string, int>* ret = countTree.Find(str);auto ret = countTree.Find(str);if (ret == nullptr){countTree.Insert(str, 1);}else{ret->_value++;}}countTree.InOrder();
}
3.二叉搜索树的相关oj题
1. 二叉树创建字符串
606. 根据二叉树创建字符串 - 力扣(LeetCode)
空括号有可能省略,也有可能不省略,需要根据图片分类讨论
总共有三种情况
1.左子树为空,右子树不为空,该括号不能省略
2.左子树,右子树都为空,该括号省略
3.左子树为空,右子树不为空,该括号省略
class Solution {
public:string tree2str(TreeNode* root) {if (root == nullptr)return "";//左子树为空,右子树不为空,该括号不能省略//左子树,右子树都为空,该括号省略//左子树为空,右子树不为空,该括号省略string str = to_string(root->val);//左子树不为空,或者左子树为空,右子树不为空,加括号if (root->left || root->right){str+="(";str+=tree2str(root->left);str+=")";}//如果右子树不为空if(root->right){str+="(";str+=tree2str(root->right);str+=")";}return str;}
};
2. 二叉树的分层遍历1
102. 二叉树的层序遍历 - 力扣(LeetCode)
由于C++有vector这个容器,因此二维数组实现起来非常方便
只需要创建vector<vector<int>> 类型的对象即可
同时创建一个levelSize的变量,也就是一层中有多少个节点
每次循环,除了将该层节点存入一个vector中,还将下一层的非空节点,存入栈中
class Solution {
public:vector<vector<int>> levelOrder(TreeNode* root) {//定义一个levelsize来确定现在出的是第几行,保证一行一行出queue<TreeNode*> q1;vector<vector<int>> vv;int Levelsize = 0;if (root){q1.push(root);Levelsize = 1;}while (!q1.empty()){vector<int> v;while (Levelsize--){//栈顶元素出队列TreeNode* front = q1.front();q1.pop();v.push_back(front->val);//如果该节点的左子树不为空,则把相应节点入队列if (front->left){q1.push(front->left);}//如果该节点的右子树不为空,则把相应节点入队列if (front->right){q1.push(front->right);}}//把对应的v压入vv中vv.push_back(v);Levelsize = q1.size();}return vv;}
};
3. 二叉树的分层遍历2
107. 二叉树的层序遍历 II - 力扣(LeetCode)
先实现层序遍历,再reverse一下即可
4. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先
236. 二叉树的最近公共祖先 - 力扣(LeetCode)
本道题有两种思路来解决
第一种,两个节点假如分别在左子树和右子树,则它们的根节点必定是公共节点
我们先实现一个判断节点是否在一棵树里面的函数InTree
然后直接递归即可,如果两个节点都在右子树,则公共祖先不可能在左子树;同理,假如两个节点
都在左子树,则公共祖先不可能在右子树;途中假如其中一个节点是根节点,则它就是两者的公共
祖先
class Solution {bool InTree(TreeNode* root, TreeNode* x){if (root == nullptr)return false;if (root == x)return true;return InTree(root->left,x) || InTree(root->right,x);}
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {//方法一,两个节点假如分别在左子树和右子树,则它们的根节点必定是公共节点if (root == nullptr)return nullptr;//假如其中一个节点是根节点,则它就是两者的公共祖先if (root == p || root == q)return root;//p在左子树或者在右子树bool pInleft = InTree(root->left,p);bool pInright = !pInleft;//q在左子树或者在右子树bool qInleft = InTree(root->left,q);bool qInright = !qInleft;//如果两者都在左子树,则公共节点一定在左子树if (pInleft && qInleft)return lowestCommonAncestor(root->left,p,q);//如果两者都在右子树,则公共节点一定在右子树else if(pInright && qInright)return lowestCommonAncestor(root->right,p,q);else return root;}
};
第一种思路虽然可以解决问题,但是时间效率上却不高
第二种思路在时间效率上就会高于第一种思路,但是实现起来却不太容易
总体思路为求出两个节点的路径,然后求两个路径的交点,该交点即为公共节点
如何求出从根节点到目标节点的路径呢?
我们采取先序遍历的方式,不管三七二十一都先把节点压入栈中,假如在某一个节点,在它的左子
树中,我们没有找到目标节点,在右子树中,也还是没找到目标节点,那该节点一定不在路径当
中,就可以把该节点直接pop掉
class Solution {
public:bool GetPath(TreeNode* root, stack<TreeNode*>& Path,TreeNode* x){//只要节点不为空,就入栈if (root == nullptr)return false;Path.push(root);//假如遇到了目标节点,则剩下的就不需要再递归if (root == x)return true;//先序遍历if (GetPath(root->left,Path,x))return true;if (GetPath(root->right,Path,x))return true;//假如左子树,右子树都不存在该节点,则该节点必定不是该路径上的一点,需要pop掉Path.pop();return false;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {//法二:求出两个节点的路径,然后求两个路径的交点,该交点即为公共节点stack<TreeNode*> pPath;stack<TreeNode*> qPath;GetPath(root,pPath,p);GetPath(root,qPath,q);//假如两者长度不一致,则先调整为一致长度while (pPath.size() != qPath.size()){if (pPath.size() > qPath.size())pPath.pop();elseqPath.pop();}//现在两者同样长度,则同时出栈,直到遇到公共祖先节点while (pPath.top() != qPath.top()){pPath.pop();qPath.pop();}return pPath.top();}
};
5. 二叉树搜索树转换成排序双向链表
二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com)
这道题的难度在于它只允许直接在原树上操作,需要充分理解递归,建立函数栈帧的过程,才能
对这道题的破解有一定思路
class Solution {
public:void _Convert(TreeNode* cur,TreeNode*& prev){if(cur == nullptr)return;_Convert(cur->left,prev);//核心代码cur->left = prev;//只有前驱节点指针不为空,才指向curif(prev){prev->right = cur;}prev = cur;_Convert(cur->right,prev);}TreeNode* Convert(TreeNode* pRootOfTree) {//观察双向链表,可知要采取中序遍历的方式//为了调节节点的指向,我们定义一个前驱指针和后驱指针TreeNode* prev = nullptr;_Convert(pRootOfTree,prev);//已知根节点,找双向循环链表的头节点TreeNode* head = pRootOfTree;while (head && head->left){head = head->left;}return head;}
};
6. 根据一棵树的前序遍历与中序遍历构造二叉树
105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)
利用类似快排的思想,先从前序遍历中确定根节点,再从中序遍历中找它的左子树和右子树
自下而上把它们链接起来
PS:中序遍历是左,根,右的结构,我们遍历先序遍历,是采用cur引用从前往后遍历,因此链接
的时候,也是先是左孩子链接,然后是右孩子链接
class Solution {
public:TreeNode* _buildTree(vector<int>& preorder, \vector<int>& inorder,int& cur,int begini,int endi){ if (begini > endi)return nullptr;TreeNode* root = new TreeNode(preorder[cur]);//分割出左右区间,从中序遍历序列中,找出前序遍历的根节点int rooti = begini;while (rooti <= endi){if(preorder[cur] == inorder[rooti])break;elserooti++;}cur++;//[begini rooti-1] rooti [rooti+1 endi]root->left = _buildTree(preorder,inorder,cur,begini,rooti - 1);root->right = _buildTree(preorder,inorder,cur,rooti+1,endi);return root;}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {//类似快排的思想,划分子区间,再递归建树int cur = 0;return _buildTree(preorder,inorder,cur,0,inorder.size()-1);}
};
7. 根据一棵树的中序遍历与后序遍历构造二叉树
106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)
思路和上题一样,不过区别就在于后序遍历是左,右,根的结构,因此需要从后往前遍历,而且链
接的时候,要先右子树链接,再链接左子树
class Solution {TreeNode* _buildTree(vector<int>& inorder,\vector<int>& postorder,int& cur,int begini,int endi){//如何区间长度小于1,则返回空指针if (begini > endi)return nullptr;//根据后序遍历序列,建节点TreeNode* root = new TreeNode(postorder[cur]);//从中序遍历序列中找到和根节点的值相同的节点//注意不是从头(下标0)开始找,而是从当前的区间的开始begini开始找int rooti = begini;while (rooti <= endi){if (inorder[rooti] == postorder[cur])break;rooti++;}//更新根节点cur--;//找到后,就可以根据中序序列,将区间划分为三个部分//[begini,rooti - 1]rooti[rooti+1,endi]//要从右子树开始建树//因为后序遍历是左子树,右子树,根的结构,cur--得到的是右子树的根root->right = _buildTree(inorder, postorder,cur,rooti+1,endi);root->left = _buildTree(inorder, postorder,cur,begini,rooti - 1);return root;}
public:TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {if (inorder.size() == 0 || postorder.size() == 0) return nullptr;//后序遍历,从后往前才是根int cur = postorder.size() - 1;return _buildTree(inorder,postorder,cur,0,inorder.size() - 1);}
};