《剑指Offer》二叉树遍历(前中后序、广度优先、深度优先)

0前言

  • 7 重建二叉树(递归)
  • 8 二叉树的下一节点
  • 26 树的子结构(递归)
  • 27 二叉树的镜像(递归)
  • 28 对称的二叉树(递归)
  • 32 从上到下打印二叉树(广度遍历
  • 33 二叉搜索树的后序遍历序列(递归)
  • 34 二叉树中和为某一值的路径(深度遍历、前序遍历首先访问根节点)
  • 37 序列化二叉树(前序遍历)、反序列化二叉树
  • 55 二叉树的深度

7 重建二叉树

题目描述

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

/**
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {//返回节点指针
        if(pre.empty()||vin.empty())
            return NULL;
        //0. 创建需要返回的节点
        TreeNode* pNode=new TreeNode(pre[0]);
        //1. 根在中序遍历位置
        int root=pre[0];
        int left_length=0;
        while(vin[left_length]!=root){
            left_length++;
        }
        //2. 递归左子树、右子树
        vector<int> left_pre,right_pre,left_vin,right_vin;
        for(int i=0;i<left_length;i++){
            left_pre.push_back(pre[i+1]);//用push_back()添加元素
            left_vin.push_back(vin[i]);
        }
        for(int j=left_length+1;j<pre.size();j++){
            right_pre.push_back(pre[j]);
            right_vin.push_back(vin[j]);
        }
        
        pNode->left=reConstructBinaryTree(left_pre,left_vin);
        pNode->right=reConstructBinaryTree(right_pre,right_vin);
        return pNode;
    }
};

注意:

1、步骤0 创建新节点换种方式是错误的:

TreeNode *pNode;
pNode->val=pre[0];
pNode->left=NULL;
pNode->right=NULL;

第一句只是定义了个野指针,指针地址是随机的,不会自动分配内存。第二句运行已经在非法访问其他内存了,可能会造成其他程序内存破坏,导致异常死机。

正确做法:TreeNode* pNode=new TreeNode(pre[0]); //创建新节点时,指明数值。

2、vector添加元素用push_back();不能直接用=赋值


8 二叉树的下一节点

题目描述

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode)
    {
        if(pNode==nullptr)
            return nullptr;
        
        TreeLinkNode* pNext=nullptr;
        //1. 有右子树,右子树最左节点
        if(pNode->right!=nullptr){
            TreeLinkNode* pright=pNode->right;
            while(pright->left!=nullptr){
                pright=pright->left;//更新
            }
            pNext=pright;
        }
        //2. 无右子树,且为左子树,父节点
        //3. 无右子树,且为右子树,溯源至根父节点
        else if(pNode->next!=nullptr){
            TreeLinkNode* pCurrent=pNode;
            TreeLinkNode* pParent=pNode->next;
            while(pParent!=nullptr&&pParent->right==pCurrent){//更新当前节点和父节点
                pCurrent=pParent;
                pParent=pParent->next;
            }
            pNext=pParent;
        }
        
        return pNext;
        
    }
};

思路:要画图根据具体情况来划分类,然后分别进行处理。


26 树的子结构

题目描述

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if(pRoot1==NULL||pRoot2==NULL)
            return false;
        return isSubtree(pRoot1,pRoot2)||HasSubtree(pRoot1->left,pRoot2)||HasSubtree(pRoot1->right,pRoot2);
    }
    bool isSubtree(TreeNode* root1, TreeNode* root2){
        if(root2==NULL)//先判断root2是否遍历,否则交换顺序会出错!!!
            return true;
        if(root1==NULL)
            return false;
        if(root1->val!=root2->val)//递归终止条件,数值如果不相等就是错的
            return false;
        return isSubtree(root1->left,root2->left)&&isSubtree(root1->right,root2->right);
    }
};

思路:

1、使用递归要把终止条件放好,否则就会出错。递归情况下用 if 判断,表明如果不触发if 语句就会一直保持之前状态。

2、root1和root2的判断顺序错的话就错了,在大多情况为错的情况下,只要有一次对的就要格外珍惜,所以先判断root2,true情况。

错误标志:

1、数值不相等

2、root1=NULL,主数遍历到头都没出现true情况。


27 二叉树的镜像

操作给定的二叉树,将其变换为源二叉树的镜像。

class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(pRoot==NULL)
            return;
        //1. 实现相互交换
        TreeNode* temp=pRoot->left;
        pRoot->left=pRoot->right;
        pRoot->right=temp;
        //2. 递归
        if(pRoot->left!=NULL)
            Mirror(pRoot->left);
        if(pRoot->right!=NULL)
            Mirror(pRoot->right);
        
    }
};

28 对称的二叉树

题目描述

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
        if(pRoot==NULL)
            return true;
        
        return fun(pRoot,pRoot);//需要比对两个二叉树才行
    }
    
    bool fun(TreeNode* root1,TreeNode* root2){
        if(root1==NULL&&root2==NULL)//正确标志
            return true;
        if(root1==NULL||root2==NULL)//错误标志
            return false;
        if(root1->val!=root2->val)
            return false;
        return fun(root1->left,root2->right)&&fun(root1->right,root2->left);
    }

};

错误标志:

1、两个数值不同

2、不是满二叉树,有的节点对应的是NULL

32 从上到下打印二叉树(广度遍历

从上往下打印出二叉树的每个节点,同层节点从左至右打印。

class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        if(root==NULL)
            return vec;
        // 用队列,元素类型为TreeNode*
        queue<TreeNode*> q;
        q.push(root);//初始化
        // 为了输出vector类型
        vector<int> vec;
        while(q.size())
        {
            vec.push_back(q.front()->val);
            if(q.front()->left)//先左后右顺序放进队列中
                q.push(q.front()->left);
            if(q.front()->right)
                q.push(q.front()->right);
            q.pop();
        }
        return vec;

    }
};

思路:利用队列queue先进先出 

32.1 分行从上到下打印二叉树

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

class Solution {
public:
        vector<vector<int> > Print(TreeNode* pRoot) {//输出是二重vector
            //1. 初始化
            vector<vector<int> > vec;
            vector<int> vec1;
            if(pRoot==NULL)
                return vec;
            // 队列初始化,赋初值
            queue<TreeNode*> q;
            q.push(pRoot);
            int tobeprint=1;
            int nextprint=0;
            //2. 队列和返回值更新
            while(!q.empty()){
                TreeNode* pNode=q.front();
                vec1.push_back(pNode->val);//打印
                
                if(pNode->left){// 把左右子树加进去
                    q.push(pNode->left);
                    nextprint++;
                }
                if(pNode->right){
                    q.push(pNode->right);
                    nextprint++;
                }
                q.pop();//可以删除这个节点了
                tobeprint--;//当前层少1个
                // 3. 换行,更新当前节点数,放进总的vec中,清空vec1
                if(tobeprint==0){
                    tobeprint=nextprint;
                    nextprint=0;
                    
                    vec.push_back(vec1);
                    vec1.clear();
                }
            }
            return vec;
        }
};

思路:比上一题多立了两个flag。 

32.2 之字形打印二叉树

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int> > vec;
        vector<int> vec1;
        if(pRoot==NULL)
            return vec;
        //1. 初始化,两个stack
        stack<TreeNode*> level[2];
        level[0].push(pRoot);//第一行
        // 2. 只要stack有一个非空就在循环
        while(!level[0].empty()||!level[1].empty()){
            vector<int> vec1;//作为临时变量
            // 3.处理奇行,保存到偶行(先左后右)
            if(!level[0].empty()){
                while(!level[0].empty()){
                TreeNode* pNode=level[0].top();
                level[0].pop();
                vec1.push_back(pNode->val);
                if(pNode->left){
                    level[1].push(pNode->left);
                }
                if(pNode->right){
                    level[1].push(pNode->right);
                }
                    }
                vec.push_back(vec1);
                vec1.clear();
            }
            
            // 4.处理偶行,保存到奇行(先右后左)
            if(!level[1].empty()){
                while(!level[1].empty()){
                TreeNode* pNode=level[1].top();
                level[1].pop();
                vec1.push_back(pNode->val);
                if(pNode->right){
                    level[0].push(pNode->right);
                }
                if(pNode->left){
                    level[0].push(pNode->left);
                }
                }
                vec.push_back(vec1);
                vec1.clear();
            }
        }
        return vec;
    }
};

思想:用两个栈,分奇偶行处理。

33 二叉搜索树的后序遍历序列

题目描述

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

思路:二叉搜索树(左<根<右)、后序遍历(左右根),题目化简为:根据后续遍历分别找到左右子树然后递归即可。

注意:1、需要判断新的左右子树是否存在,然后才递归。

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        if(sequence.size()==0)
            return false;
        
        int length=sequence.size();
        int root=sequence[length-1];//根数值
        vector<int> seq1,seq2;
        // 1. 分别找到左右子树(同时判断是否合法的二叉搜索树)
        int i=0;// 这里用i作为截断数字即可,无须重新定义新变量。
        for(;i<length-1;i++){
            if(sequence[i]>root){
                break;
            }
            else
                seq1.push_back(sequence[i]);//同时生成左右子树
        }
        for(int j=i;j<length-1;j++){
            if(sequence[j]<root){//判断是否合法的二叉搜索树
                return false;
            }
            else
                seq2.push_back(sequence[j]);
        }
        // 2. 对左右子树递归
        bool left = true,right = true; // 看左右子树是否是二叉搜索树!!!
        if(seq1.size()) VerifySquenceOfBST(seq1);//需要判断,因为有只有左或右子树情况。
        if(seq2.size()) VerifySquenceOfBST(seq2);
         
        return (left&&right);
    }
};

34 二叉树中和为某一值的路径

题目描述

输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

class Solution {
public:
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        vector<vector<int> > vec;
        vector<int> trace;
        if(root==NULL)
            return vec;
    int currnumber=0;
        // 深度遍历dfs思想
        dfs(root,expectNumber,currnumber,vec,trace);
            return vec;
    }
    // 主函数
    void dfs(TreeNode* root,int s,int currnumber,vector<vector<int> >& vec,vector<int>& trace){
        currnumber+=root->val;
        trace.push_back(root->val);// 路径
        // 判断结束,到叶子
        if(!root->left&&!root->right){
            if(s==currnumber){
                vec.push_back(trace);
            }
        }
        // 递归,左右子树
        if(root->left) dfs(root->left,s,currnumber,vec,trace);
        if(root->right) dfs(root->right,s,currnumber,vec,trace);
        // 返回父节点,路径上删除当前节点
        // 删除path的最后一个元素,它表示当前叶节点和根节点形成的路径不满足条件,删除叶节点,返回它的父节点
        trace.pop_back();
    }
};

思路:深度遍历dfs,

  •  递归先序遍历树, 把结点加入路径。
  •      若该结点是叶子结点则比较当前路径和是否等于期待和。
  •     弹出结点,每一轮递归返回到父结点时,当前路径也应该回退一个结点

55 二叉树的深度

题目描述

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

class Solution {
public:
    int TreeDepth(TreeNode* pRoot)
    {
        if(pRoot==NULL)
            return 0;
        int nleft=TreeDepth(pRoot->left);
        int nright=TreeDepth(pRoot->right);
        return nleft>nright?nleft+1:nright+1;
    }
};

37 序列化二叉树

思路:和遍历二叉树相似,这里采用前序遍历方式。当遇到根节点时,追加“$” 并退出递归。否则追加二叉树根节点值,并追加逗号。递归实现左右子树的序列化。1,2,4,$,$,$,3,5,$,$,6.$,$

class Solution {
public:
    char* Serialize(TreeNode *root) {    //序列化二叉树
        if(root==NULL){
            return NULL;
        }
        // 利用string拼接序列化的字符串。从string 转为char*
        string str;
        char* ret=new char[str.length()+1];
        aserialize(root,str);
        int i=0; 
        for(;i<str.length();i++)
            ret[i]=str[i];
        ret[str.length()]='\0';//结尾加入’\0’标记结束
        return ret;
    }
    void aserialize(TreeNode *root,string& str){
        if(root==NULL){
            str+='$';//遇到nullptr指针,添加特殊符号$
            return;
        }
        // 前序,根左右,根用string拼接作为输出,
        string r=to_string(root->val);// to_string将数值转化为字符串
        str+=r;
        str+=',';
        aserialize(root->left,str);
        aserialize(root->right,str);
    }
};

37 反序列化二叉树

思路:根据字符串序列1,2,4,$,$,$,3,5,$,$,6.$,$得到二叉树。7 重建二叉树,核心在于对于节点的创建new ,以及递归左右子树。

C语言中不存在引用,所以当你试图改变一个指针的值的时候必须使用二级指针

    TreeNode* Deserialize(char *str) {//反序列化二叉树
        if(str==NULL)
            return NULL;
        TreeNode* ret=Deserialize(&str);
        return ret;
    }
    TreeNode* Deserialize(char **str){//二重指针
        // 1.出现NULL,指针后移
        if(**str=='$'){
            ++(*str);
            return NULL;
        }
        //2. 获得两个逗号之间的数字'123'---123字符转数值
        int num=0;
        while(**str!='\0';&&**str!=','){
            num=num*10+(**str-'0');
            ++(*str);
        }
        TreeNode* root=new TreeNode(num);//新建节点初始化
        //3. 是否到达尾节点
        if(**str=='\0'){
            return root;
        }
        else
            ++(*str);
        // 4.递归左右子树
        root->left=Deserialize(str);
        root->right=Deserialize(str);
        return root;
    }
发布了176 篇原创文章 · 获赞 84 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/try_again_later/article/details/90719207