二叉树解题框架思路
首先要确定,这道题用什么遍历?一定能找到一种遍历模板解决这道题,所以一定要熟悉遍历模板
先序/中序/后续=>用递归
层次遍历=>while循环+队列
如何确定用什么遍历
思考一个节点需要做什么。
如果是二叉搜索树,一般考虑中序遍历
以为二叉搜索树中序遍历可以得到一个升序的数组
如果想得到降序的数组就先右子树、根节点、左子树
二叉树各种遍历的框架
层次遍历
while+队列
队列中队首是即将遍历的子树的根节点
queue<TreeNode> q;
void(返回值根据题目要求) traverse(TreeNode *root)
{
q.push(root);//根节点入队
while(!q.empty())//如果队列不为空
{
TreeNode node1=q.front();//队首出队
q.pop();
//层次遍历要进行操作的代码开始**************************
if(node1==NULL)
{ //null的一些特殊处理,若无特殊处理
continue;
}
//如果node1!=NULL,要进行的操作
//层次遍历要进行操作的代码结束******************************
q.push(node1->left);
q.push(node1->right);
}
return (题目要求的类型);
}
二叉树和图的区别就是,二叉树对于叶子结点,设置左右子节点为NULL,所以有一个对叶子结点的处理
图对于没有出度的节点,不会设置指向一个空节点的指针,所以在遍历相邻节点的时候,就可以自动的停在终端节点处
前序中序后续
void pre(TreeNode *root) {
if (root == null) {//NULL的时候可能需要的特殊处理
return;
}
/****** 先序遍历的位置 ******/
//先序遍历根节点要进行的操作
/***********************/
pre(root.left, sb);
pre(root.right, sb);
return;
}
void post(TreeNode *root) {
if (root == null) {//NULL的时候可能需要的特殊处理
return;
}
post(root.left, sb);
post(root.right, sb);
/****** 后序遍历的位置 ******/
//先序遍历根节点要进行的操作
/***********************/
return;
}
void in(TreeNode *root) {
if (root == null) {//NULL的时候可能需要的特殊处理
return;
}
in(root.left, sb);
/****** 后序遍历的位置 ******/
//先序遍历根节点要进行的操作
/***********************/
in(root.right, sb);
return;
}
如何确定前序后续中序中的操作
只需要找一个点作为根节点,当你站在这个点的上的时候,j需要做什么,就是你需要定义的操作
同时注意,对于树的递归,通常函数参数都是根节点,也就是一个点,然后对左右子节点操作(递归-实际是左右子树),但是如果函数是对两个节点之间的操作,并且这两个节点不一定是这个根节点的左右子节点,那么此时函数参数可能需要两个点,才能完成这个功能
序列化和反序列化
序列化只要修改递归遍历的模板, 在操作部分的代码中把节点加入到序列化对象中,然后返回序列化对象即可,主要看反序列化
反序列化:
1.先确定根节点 root(先序-序列化对象最左边,后续-序列化对象最右边)
2.在序列化对象中删除根节点值
3.判断是否是空指针序列化后的值,是的话处理然后返回
不是的话生成根节点
4.然后遵循递归遍历的规则,递归生成根节点左右子树。
下面假设序列化对象是vector seq
前序
只需要修改递归模板中的操作部分代码:
1.确定根节点,然后把根节点从序列化对象中删除
递归部分代码不便:
2.递归反序列左子树
3.递归反序列化右子树
直接在先序遍历的模板上修改,方便对比
TreeNode* de_seq_pre(vector<int> seq) {
// if (root == null) {//NULL的时候可能需要的特殊处理
// return;
// }
if(seq.size()==0) return NULL;//对应pre遍历root==NULL的情况
/********** 先序遍历操作的位置 ***********/
int root_val=seq.front();//序列化对象最左边就是根节点的值
seq.erase(seq.begin());//在序列化对象中删除根节点
if(root==-1(序列化中表示NULL的值) return NULL;//这里和前面SIZE==0不同,这里表示递归到叶子结点了
TreeNode *root=new TreeNode(root_val);
/**********************************/
root->left=de_seq_pre(seq);
root->right=de_seq_pre(seq);
return root;
}
后续(特殊)
后序遍历的反序列化,不能直接修改后序递归遍历中操作部分的代码,因为后续递归遍历中,最后才到根节点部分的操作,但是反序列化要先确定根节点
正确的操作步骤:
1.先确定根节点,序列化对象中最右边的元素,然后从序列化对象中删除
2.递归反序列化右子树 顺序不能改,因为序列化对象中,从有往左,是根节点,先右子树,然后左子树
3.递归反序列化左子树
TreeNode* de_seq_post(vector<int> seq) {
/* 序列化
vector<int> seq;//序列化返回对象
vector<int> seq_post(TreeNode *root)//后序遍历序列化函数
{
if (root == NULL){
seq.push_back(-1);//用-1表示空指针(递归到叶节点了)
return;}
post(root->left);
post(root->right);
// 后序遍历操作的位置*****************
seq.push_back(root->val);
//******************************
return;
}
*/
-------------------------------------------------
//反序列化
if(seq.size()==0) return NULL;//对应树为空的情况
//1.确定根节点
int root_val=seq.back();
seq.pop_back();//在序列化对象中删除根节点
if(root_val==-1(序列化的时候表示NULL的值)) return NULL;//表示递归到叶节点
TreeNode *root=new TreeNode(root_val);
//2.递归序列化右子树,左子树
root->right=de_seq_post(seq);
root->left=de_seq_post(seq);
return root;
}
层次
序列化
只需要在*****中间修改代码,如下
vector<int> seq;
void traverse(TreeNode *root)
{
queue<TreeNode> q;
q.push(root);
while(!q.empty())
{
TreeNode *node1=q.front();
q.pop();
//****************层次遍历对每个节点要进行的操作************************
if(node1==NULL)
{
//叶节点的操作:seq.push_back(-1);//-1表示空指针,即遍历到叶子结点了
continue;
}
//非叶子结点要进行的操作 seq.push_back(root->val);
//**************************************************************************
q.push(node1->left);
q.push(node1->right);
}
return;
}
//二叉树的层次遍历,扩展一下就是BFS
二叉树: 图的BFS:
1.根节点入队,开始while循环 1.选择一个点开始BFS,这个点入队,开始while循环
while{ while{
2.node=队首元素出队 2.node=队首元素出队
3.--------------------- 3.-------------------------
3.1 if node==NULL(即到叶子结点了): if 到终点(最短路找到):
针对空指针操作,continue return
3.2 非空指针的操作 --------------------------
4.node的左右子树入队 4.遍历node相邻节点:
if(满足可以走的条件) {
这个相邻点的step=node.step+1;
这个相邻点入队;
标记已经走过;
}
} }
node左右子树入队,其实就是node相邻节点入队,因为层次遍历,所以左右子树一定是没有走过的,所以就不用像BFS一样进行判断
反序列化
每一个非空节点都会对应两个子节点,那么反序列化的思路也是用队列进行层级遍历,同时用索引 i 记录对应子节点的位置:
每处理一个节点,i+1就是左子树,i+2就是右子树,这样每个节点就对应着两个节点而且根据层次遍历的规律,这样顺次下去,刚好是对应的父节点和子节点
还是按照:
1.确定根节点,序列化对象的最左边,
2.在序列化对象中删除根节点的值
3.将根节点加入父节点队列中
4.根据层次遍历的特点,找左子树
5.找右子树
TreeNode* de_seq_tr(vector<int> seq)
{
if(seq.size()==0) return NULL;//空树,对应root==NULL
queue<TreeNode> pq;//队列中的节点都是父节点(叶子结点也是父节点,子节点为空指针的父节点)
//所以只要不是空指针,都入队
//1.确定根节点,根节点从序列化对象中删除
TreeNode *root=new TreeNode(seq.front());
seq.erase(seq.begin());
for(int i=1;i<seq.size()-1;i++)//处理序列化对象中每个点
{
TreeNode *root=pq.front();
pq.pop();
//根据层次遍历,找左子树
int left=seq[i++];
if(left==-1) root->left=NULL;
else{
root->left=new TreeNode(left);
q.push(root->left);//不是空指针,就对应子节点,就入队
}
//根据层次遍历,找右子树
int right=seq[i++];
if(right==-1) root->right=NULL;
else{
root->right=new TreeNode(right);
q.push(root->right);
}
}
return root;
}
重复子树的例题
【读完题后的思路】
给定两个节点A和B,怎么知道以A为根的子树和以B为根的子树有相同的结构呢?
需要知道A的子树结构,和B的子树结构
怎么知道以某个节点为根的子树的结构?
后序遍历,因为应该从最小的子树结构开始比较,因为可能上层重复,那么它的子树一定也重复
怎么比较结构是否相同?
把遍历结果序列化,比较两个序列是否相同即可
多次重复的只返回一次?
用哈希表存储,unordered_map<string,int>
所以只需要通过后序遍历,序列化二叉树的子树,然后存储到hash表中,每序列化完一个子树,就判断一下hash表中是否出现过,如果有的话,就放入答案列表中