C++学习笔记-二叉树(2)

8.对称二叉树 101

判断二叉树是否对称,就要比较根节点的左右子树是否相等,那么要比较的就不是左右节点,而是左右子树的内侧和外侧元素是否分别相等

遍历顺序只能是后序遍历,因为要通过左右孩子(即内侧和外侧节点)的比较结果,来判断这两棵子树是否对称。

使用递归。

第一步:确定函数参数及返回值。因为要判断根节点的左右子树是否可翻转,所以传入一个左节点和一个右节点;判断是否对称,所以返回值类型为Bool。

第二步:确定递归的终止条件。当左节点为空而右节点不为空时,FALSE;当左节点不为空而右节点为空时,FALSE;当左右节点均为空时,也算对称,TRUE;当左右节点均不为空但数值不相等时,FALSE。

第三步:确定单层递归逻辑。在左右节点均不为空且数值相等时,就要向下一层比较了。比较下一层时,就是比较两个中间节点的外侧和内侧是否分别相等,即左节点的左孩子和右节点的右孩子是否相等左节点的右孩子和右节点的左孩子是否相等。二者均相等的话,说明这两棵子树是对称的,将结果返回上一层。

class Solution {
public:
    bool compare(TreeNode* left, TreeNode* right)
    {
        //先处理有节点为空的情况
        if(left!=NULL && right==NULL) return false;
        else if(left==NULL && right !=NULL) return false;
        else if(left==NULL && right==NULL) return true;
        //节点均不为空但数值不相等
        else if(left->val != right->val) return false;

        //节点均不为空且数值相等,就向下一层判断
        //此时开始单层递归
        bool outside=compare(left->left,right->right);  //外侧:左节点的左孩子和右节点的右孩子
        bool inside=compare(left->right,right->left);  //内侧:左节点的右孩子和右节点的左孩子
        bool isSame=outside && inside;  //二者都相等,说明左右子树对称
        return isSame;
    }

    bool isSymmetric(TreeNode* root) {
        if(root==NULL) return true;
        return compare(root->left,root->right);
    }
};

100.相同的树

class Solution {
public:
    bool compare(TreeNode* p, TreeNode* q)
    {
        //传入的是两棵二叉树的根节点
        //先确定终止条件
        if(p==NULL && q!=NULL) return false;
        else if (p!=NULL && q==NULL) return false;
        else if (p==NULL && q==NULL) return true;
        else if (p->val != q->val) return false;

        //单层递归逻辑
        bool compareLeft=compare(p->left,q->left);
        bool compareRight=compare(p->right,q->right);
        bool isSame=compareLeft && compareRight;
        return isSame;
    }
    
    
    bool isSameTree(TreeNode* p, TreeNode* q) {
        return compare(p,q);
    }
};

572.另一棵树的子树(没完全掌握)

判断一棵树是否是另一棵树的子树,有以下五种情况

(1)这两棵树本身相等;

(2)是这棵树的左子树;

(3)是这棵树的右子树;

(4)是左子树的子树;

(5)是右子树的子树。

前三种情况直接调用二叉树比较函数即可,但后面两种的根节点发生了变化,要重新调用传入根节点的函数,传入新的根节点以原根节点的子树的根节点为新的根节点

class Solution {
public:
    //构造比较两个树是否相等的函数
    bool compare(TreeNode* p, TreeNode* q)  //传入两个树的根节点
    {
        if(p!=NULL && q==NULL) return false;
        else if(p==NULL && q!=NULL) return false;
        else if(p==NULL && q==NULL) return true;
        else if(p->val!=q->val) return false;

        bool compareLeft=compare(p->left,q->left);
        bool compareRight=compare(p->right,q->right);
        bool isSame=compareLeft && compareRight;
        return isSame;
    }


    bool isSubtree(TreeNode* root, TreeNode* subRoot) {
        if(root==NULL) return false;   //把a二叉树都遍历完了还没匹配上,说明不成立
       
       //b树是a树的子树的几种情况
        return compare(root,subRoot)    //1.ab这两棵树本身是否相等
                || compare(root->left,subRoot)  //2.b这棵树是a的左子树?
                || compare(root->right,subRoot) //3.b这棵树是a的右子树?
                || isSubtree(root->left,subRoot)   //4.不局限于a左子树本身,b可能还是左子树的子树,因此要重新调用函数,把新的根节点传入
                || isSubtree(root->right,subRoot);  //5.b可能是a右子树的子树,所以传入新的根节点
    }
};

9.二叉树的最大深度 104

迭代法,在二叉树的层序遍历里出现过

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(root==NULL) return 0;
        int depth=0;

        queue<TreeNode*> que;
        que.push(root);

        while(!que.empty())
        {
            int size=que.size();
            depth++;

            for(int i=0; i<size; i++)
            {
               TreeNode* node=que.front();
               que.pop(); 
               if(node->left) que.push(node->left);
               if(node->right) que.push(node->right);
            }
        }

        return depth;
    }
};

递归法(后序遍历)

按理来说,求二叉树的深度,应该用前序遍历,这里为什么用后序?因为代码的逻辑实际上是求根节点的高度,根节点的高度就是二叉树的最大深度,所以用的是求高度的后序遍历。

class Solution {
public:
    //先写一个求深度的函数
    int getDepth(TreeNode* node)  //第一步:确定函数参数及返回值:传入一个二叉树节点,求的是深度,所以返回值类型为int
    {
        if(node==NULL) return 0;  //终止条件

        //单层递归逻辑
        int leftDepth=getDepth(node->left);  //左子树深度
        int rightDepth=getDepth(node->right);  //右子树深度
        int depth=1+max(leftDepth,rightDepth);  //中间,处理逻辑,左右子树的最大深度+1即为二叉树的最大深度
        return depth;

    }

    int maxDepth(TreeNode* root) {
        return getDepth(root);
    }
};

559.N叉树的最大深度

递归法(一层for循环,用时8ms)

class Solution {
public:
    
    int maxDepth(Node* root) {
        if(root==NULL) return 0;
        int depth=0;

        for(int i=0; i<root->children.size(); i++)
        {
            depth=max(depth,maxDepth(root->children[i]));
        }

        return depth+1;
    }
};

迭代法(while+两层for循环,用时16ms)

class Solution {
public:
    
    int maxDepth(Node* root) {
        if(root==NULL) return 0;
        int depth=0;

        queue<Node*> que;
        que.push(root);

        while(!que.empty())
        {
            int size=que.size();
            depth++;

            for(int i=0; i<size; i++)
            {
                Node* node=que.front();
                que.pop();
                for(Node* child : node->children)  //冒号后是要遍历的集合,冒号前是实例化一个集合中包含的元素
                {
                    if(child) que.push(child);
                }
            }
        }

        return depth;
    }
};

10.二叉树的最小深度 111

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

递归法:

求最大深度可以直接求两个子树的最大深度再+1,但求最小深度不能直接按照两个子树的最小深度+1来算,因为可能某一侧的子树为空,这时最小深度应该是不为空的子树的最小深度+1,所以要先判断一下。求最大深度不需要这一步。

class Solution {
public:
    //求最小深度的函数
    int getDepth(TreeNode* node)
    {
        if(node==NULL) return 0;
        int leftDepth=getDepth(node->left);
        int rightDepth=getDepth(node->right);

        //左子树为空而右子树不为空,则最小深度按照右子树来算
        if(node->left==NULL && node->right!=NULL)
        {
            return 1+rightDepth;
        }
        //左子树不为空而右子树为空,则最小深度按照左子树来算
        if(node->left!=NULL && node->right==NULL)
        {
            return 1+leftDepth;
        }
        //左右子树都为空或都不为空,按照最小值计算即可
        int result=1+min(leftDepth,rightDepth);
        return result;
    }

    int minDepth(TreeNode* root) {
        return getDepth(root);
    }
};

迭代法:

过程和求最大深度一样,遍历的层数就是深度,只不过是碰到左右孩子均为空的时候要直接返回。

class Solution {
public:
    
    int minDepth(TreeNode* root) {
       if(root==NULL) return 0;
       int depth=0;

       queue<TreeNode*> que;
       que.push(root);

       while(!que.empty())
       {
           int size=que.size();
           depth++;

           for(int i=0; i<size; i++)
           {
               TreeNode* node=que.front();
               que.pop();
               if(node->left) que.push(node->left);
               if(node->right) que.push(node->right);
               if(!node->left && !node->right) return depth;
           }
       }

       return depth;
    }
};

11.完全二叉树的节点个数 222

方法一:按照普通二叉树来求

递归法:后序遍历

class Solution {
public:
    //先写一个求节点个数的函数
    int getNumber(TreeNode* node)
    {
        if(node==NULL) return 0;
        int leftNum=getNumber(node->left);  //左:求左子树节点数量
        int rightNum=getNumber(node->right);  //右:求右子树节点数量
        int treeNum=leftNum+rightNum+1;    //中:处理逻辑,总节点数量=左+右+1
        return treeNum;
    }

    int countNodes(TreeNode* root) {
        return getNumber(root);
    }
};

迭代法:和层序遍历类似的流程,只是需要统计一下遍历过的节点数量

class Solution {
public:
   
    int countNodes(TreeNode* root) {
        if(root==NULL) return 0;
        int count=0;

        queue<TreeNode*> que;
        que.push(root);

        while(!que.empty())
        {
            int size=que.size();

            for(int i=0; i<size; i++)
            {
                TreeNode* node=que.front();
                que.pop();
                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
                count++;
            }
        }

        return count;
    }
};

方法二:按照完全二叉树来求(没完全掌握)

完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。

对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。

对于情况二,本身不是满二叉树,则分别递归左孩子和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树(直到叶子节点),然后依然可以按照情况1来计算。

后序遍历。

class Solution {
public:
   
    int countNodes(TreeNode* root) {
        
        //终止条件:遇到什么情况需要返回
        if(root==NULL) return 0;
        TreeNode* left=root->left;
        TreeNode* right=root->right;
        int leftDepth=0, rightDepth=0;  //为求指数方便,先初始化为0
        while(left)
        {
            left=left->left;  //一直向左往下走
            leftDepth++;  //这里感觉不对啊,实际上到第三层的时候才为1,算下来只有3个节点,实际应该有7个了
        }
        while(right)
        {
            right=right->right;  //一直向右往下走
            rightDepth++;
        }
        if(leftDepth==rightDepth)  //左右子树深度相同,说明是一棵满二叉树,可以直接利用公式计算节点数量
        {
            return (2<<leftDepth)-1;  //二进制,2左移2位等于2*2^2,左移3位等于2*2^3
            //所以前面要先初始化深度为0,就是为了避免初始深度为1造成错误
        }

        //单层递归逻辑
        //本身不是满二叉树,则分别递归左孩子和右孩子,最终一定可以递归到满二叉树上,然后按照公式来计算
        int leftNum=countNodes(root->left);   //左
        int rightNum=countNodes(root->right);  //右
        int treeNum=leftNum+rightNum+1;  //中
        return treeNum;
    }
};

12.平衡二叉树 110

递归法:(迭代法太复杂了)

二叉树节点的深度:从根节点到该节点的距离。因为是从上往下遍历,所以是前序遍历,先处理中间节点,然后再处理左右孩子。

二叉树节点的高度:从叶子节点到该节点的距离。因为是从下往上遍历,中间节点的高度需要参考左右孩子的高度,所以是先求左右孩子高度,再向上返回结果计算中间节点的高度。所以用后序遍历

我们假设返回值为-1说明不是高度平衡二叉树。因为是求高度,所以使用后序遍历。

class Solution {
public:
    //先写一个求高度的函数
    int getHeight(TreeNode* node)
    {
        if(node==NULL) return 0;   //递归的终止条件

        //单层递归逻辑
        //左:求左子树高度,若返回值为-1,说明左子树不是平衡二叉树,则该树必不是平衡二叉树,直接返回-1
        int leftHeight=getHeight(node->left);
        if(leftHeight==-1) return -1;
        //右:求右子树高度,若返回值为-1,说明右子树不是平衡二叉树,则该树必不是平衡二叉树,直接返回-1
        int rightHeight=getHeight(node->right);
        if(rightHeight==-1) return -1;

        //中:左右子树均为平衡二叉树,则将高度返回中间节点继续处理
        int result;
        if(abs(leftHeight-rightHeight)>1)   //高度差大于1,则不是平衡二叉树
        {
            result=-1;
        }
        else
        {
            result=1+max(leftHeight,rightHeight);   //是平衡二叉树,则求二叉树的高度
        }
        return result;
    }

    bool isBalanced(TreeNode* root) {
        int result=getHeight(root);
        return result==-1 ? false : true;
    }
};

13.二叉树的所有路径 257

这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。

递归之后要回溯,这样才能退回去重新开始新的一条路径。

1.递归函数参数及返回值

不需要返回值,所以是void。函数参数除了接收传入的节点之外,还需要一个vector<int>类型的数组来存放路径的节点(用vector是因为回溯的时候比较方便),还需要一个vector<string>类型的数组来存放符合要求的带->的最终结果。

2.终止条件

遍历到叶子节点就可以收集这一条路径了,但记得要把遍历到的叶子节点也加进去,不然是不完整的。刚开始路径节点是放进vector<int>类型的数组的,要处理一下转成符合要求的形式放进vector<string>类型的数组。

3.单层递归逻辑

左子树递归,然后回溯;右子树递归,然后回溯。必须要回溯,把需要弹出的节点都弹出去,才能进入下一条路径。

class Solution {
public:

    //先写一个递归函数
    void traversal(TreeNode* cur, vector<int>& path, vector<string>& result)
    {
        path.push_back(cur->val);  //中,把中放在最前面是为了把叶子节点不要漏掉

        //终止条件:到叶子节点了就可以结束遍历,路径就可以存进结果集了
        if(cur->left==NULL && cur->right==NULL)
        {
            string sPath;
            for(int i=0; i<path.size()-1; i++)   //进行一下处理,把结果变成符合要求的字符串,注意循环是到size-1,不然会多一个->
            {
                sPath+=to_string(path[i]);
                sPath+="->";
            }
            sPath+=to_string(path[path.size()-1]);  //单独处理最后一个节点,即叶子节点
            result.push_back(sPath);  //收集一条路径
            return;  //在这就返回了,所以如果把“中”放在这一部分后面的话,会漏掉叶子节点没有放进去
        }

        //单层递归逻辑:先左后右
        if(cur->left)  //判断一下是否为空,如果是空的话就不进行下面的递归了
        {
            traversal(cur->left,path,result);
            path.pop_back();   //每一次递归,都必须要跟一个回溯
        }
        if(cur->right)
        {
            traversal(cur->right,path,result);
            path.pop_back();
        }
    }

    vector<string> binaryTreePaths(TreeNode* root) {
        vector<int> path;
        vector<string> result;
        traversal(root,path,result);
        return result;
    }
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值