二叉树/二叉搜索树(BST)解题思路框架

二叉树解题框架思路

首先要确定,这道题用什么遍历?一定能找到一种遍历模板解决这道题,所以一定要熟悉遍历模板

先序/中序/后续=>用递归
层次遍历=>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表中是否出现过,如果有的话,就放入答案列表中

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值