代码随想录算法训练营Day15|层序遍历,翻转二叉树,对称二叉树

本文详细探讨了二叉树的层序遍历实现,介绍了递归和迭代方法,并特别关注了如何使用这些技术判断对称二叉树,包括对节点的比较策略和不同遍历顺序的应用。
摘要由CSDN通过智能技术生成

层序遍历

层序遍历一个二叉树,就是从左往右一层一层的遍历二叉树,这种遍历方式需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。而这种层序遍历方式就是图论中的广度优先遍历,只不过我们用在二叉树上。

使用队列实现二叉树广度优先遍历。

class Solution{
public:
   vector<vector<int>>levelOrder(TreeNode* root){
      queue<TreeNode*>que;
      if(root != NULL)que.push(root);
      vector<vector<int>>result;
      while(!que.empty()){
        int size = que.size();
        vector<int>vec;

        //这里使用固定大小size,不要使用que.size().因为que.size是不断变化的

        for(int i = 0;i < size; i++){
            TreeNode* node = que.front();
            que.pop();
            vec.push_back(node->val);
            if(node->left)que.push(node->left);
            if(node->right)que.push(node->right);
        }
        result.push_back(vec);
      }
      return result;
   }
};
//递归法
class Solution{
public:
   vod order(TreeNode* cur,vector<vector<int>>&result,int depth)
   {
    if(cur == nullptr)return;
    if(result.size() == depth)result.push_back(vector<int>());
    result[depth].push_back(cur->val);
    order(cur->left,result,depth + 1);
    order(cur->right,result,depth + 1);
   }
   vecctor<vector<int>>levelOrder(TreeNode* root){
    vector<vector<int>>result;
    int depth = 0;
    order(root,result,depth);
    return result;
   }
};

反转二叉树

思路:只要把每个节点的左右孩子翻转一下,就可以达到整体翻转的效果。这道题使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转两次。层序遍历也可以。

递归三步:

1.确定递归函数的参数和返回值

参数就是要传入节点的指针,不需要其他参数了,通常此时定下来主要参数,如果在写递归的逻辑中发现还需要其他参数的时候,随时补充。

返回值的话其实也不需要,但是题目中给出的要返回root节点的指针,可以直接使用题目定义好的函数,所以就函数的返回类型TreeNode*。

TreeNode* invertTree(TreeNode* root)

2.确定终止条件

当前节点为空的时候,就返回

if(root == NULL)return root;

3.确定单层递归的逻辑

因为先前序遍历,所以先进行交换左右孩子节点,然后左转左子树,反转右子树。

swap(root->left,root->right);
invertTree(root->left);
invertTree(root->right);
//基于上面三步,完整C++代码:

class Solution{
public:
   TreeNode* invertTree(TreeNode* root){
    if(root == NULL)return root;
    swap(root->left,root->right); //中
    invertTree(root->left); //左
    invertTree(root->right);  //右
    return root;
   }
};

迭代法


深度优先遍历

前序遍历:

class Solution{
    TreeNode* invertTree(TreeNode* root){
        if(root == NULL)retrun root;
        stack<TreeNode*>st;
        st.push(root);
        while(!st.empty()){
            TreeNode* node = st.top();  //中
            st.pop();
            swap(node->left,node->right);
            if(node->right)st.push(node->right); //右
            if(node-left)st.push(node->left); //左
        }
        return root;
    }
};

前序遍历:

class Solution{
public:
  TreeNode* invertTree(TreeNode* root){
    stack<TreeNode*>st;
    if(root != NULL)st.push(root);//判断非空,栈尾部添加元素
    while(!st.empty()){
        TreeNode* node = st.top(); //top()取出栈顶元素
        if(node != NULL){
            st.pop();  //pop()删除栈顶元素
            if(node->right)st.push(node->right);//右
            if(node->left)st.push(node->left);//左
            st.push(node);
            st.push(NULL);  //中
        }else{
            st.pop();
            node = st.top();
            st.pop();
            swap(node->left,node->right);  //节点处理逻辑
        }
    }
    return root;
  }
};

广度优先遍历

//层序遍历可以翻转这棵树,因为层序遍历也可以把每个节点的左右孩子都翻转一遍,代码如下:
class Solution{
public:
   TreeNode* invertTree(TreeNode* root){
     queue<TreeNode*>que;
     if(root != NULL)que.push(root);
     while(!que.empyu()){
        int size = que.size();
        for(int i = 0;i < size;i++){
            TreeNode* node = que.front();
            que.pop();
            swap(node->left,node->right);//节点处理,左右交换
            if(node->left)que.push(node->left);
            if(node->right)que.push(node->right);
        }
     }
     return root;
   }
};

不能用递归的中序遍历,因为使用递归的中序遍历,某些节点的左右孩子会翻转两次。

但也可以避免。

class Solution{
public:
    TreeNode* invertTree(TreeNode* root){
        if(root == NULL)return root;
        invertTree(root->left);  //左
        swap(root->left,root->right); //中
        invertTree(root->left);//注意:这里仍然要遍历左孩子,因为中间节点已经翻转了
        return root;
    }
};

但这不算真正的递归中序遍历了。

但使用迭代方式统一写法的中序遍历是可以的。

class Solution{
public:
TreeNode* invertTree(TreeNode* root){
    stack<TreeNode*>st;
    if(root != NULL)st.push(root);
    while(!st.empty()){
        TreeNode* node = st.top();
        if(node != NULL){
            st.pop();
            if(node->right)st.push(node->right);//右
            st.push(node);//中
            st.push(NULL);
            if(node->left)st.push(node->left);//左
        }else{
            st.pop();
            node = st.top();
            st.pop();
            swap(node->left,node->right); //节点处理逻辑
        }
    }
    return root;
}
};

上面是用栈来遍历,而不是用指针,避免了递归法中翻转了两次的情况。

总结:对于二叉树的问题,解题前想清楚究竟是前中后序遍历,还是层序遍历。

对称二叉树

判断对称二叉树要比较的是哪两个节点,要比较的不是左右节点,而是两个树,这个树是根节点的左右子树,所以在递归遍历的过程中,也要同时遍历两棵树。

如何比较?比较两个子树的里侧和外侧元素是否相等。

遍历的顺序只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内测节点和外侧节点是否相等。

正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。

但都可以理解算是后序遍历,尽管已经不是严格上在一个树上进行遍历的后序遍历了。

后序可以理解为一种回溯

递归法

递归三部曲:

1.确定递归函数的参数和返回值

因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。

返回值自然是bool类型。

代码如下:

bool comppare(TreeNode* left,TreeNode* right)

2.确定终止条件

要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。

节点为空的情况有:(注意!!!比较的是左右节点,是节点,不是左右孩子)

左节点为空,右节点不为空,不对称,return false

左不为空,右为空,不对称return false

左右都为空,对称,返回true

此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:

左右节点都不为空,比较节点数值,不相同就return false

此时左右节点不为空,且数值也不相同的情况我们也处理了。

代码如下:

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;

注意最后一种情况,没用else 而是else if,因为我们把以上情况都排除之后,剩下的就是左右节点都不为空,且数值相同的情况。

3.确定单层递归的逻辑

此时才进入单层递归的逻辑层递归的逻辑就是处理 左右节点都不为空,且数值相同的情

比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。

比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。

比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。

如果左右都对称就返回true,有一侧不对称就返回false.

bool outside = compare(left->left,right->right)//左子树:左、右子树;右
bool inside = compare(left->right,right->left);//左子树:右、右子树:左
bool isSame = outside && inside; //左子树:中、 右子树:中(逻辑处理)
retrun isSame;

如上代码中,我们可以看出使用的遍历方式,左子树左右中,右子树右左中,所以也叫做“后序遍历(尽管不是严格的后序遍历)。

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);
   }
};

迭代法

翻转二叉树也可以使用迭代法,但要注意,这里迭代法不是前中后序的迭代写法,因为本题的本质是判断两个树是否是相互翻转的,其实已经不是所谓二叉树遍历的前中后序的关系了。

这里我们可以使用队列来比较两个树(根节点左右子树)是否相互翻转,(注意这不是层序遍历)

使用队列

通过队列来判断根节点的左子树和右子树的内侧和外侧是否相等

class Solution{
public:
   bool isSymmetric(TreeNode* root){
    if(root == NULL)return true;
    queue<TreeNode*>que;
    que.push(root->left);//将左子树节点加入队列
    que.push(root->right);//将右子树头节点加入队列

    while(!que.empty()){//接下来就要判断这两个树是否相互翻转
       TreeNode* leftNode = que.front();que.pop();
       TreeNode* rightNode = que.front();que.pop();
       if(!leftNode && !rightNode){//左节点为空,右节点为空,此时说明是对称的
        continue;
       }

       //左右一个节点不为空,或者都不为空但数值不相同,返回false
       if((!leftNode || rightNode || (leftNode->val != rightNode->val))){
        return false;
       }
       que.push(leftNode->left);//加入左节点左孩子
       que.push(rightNode->right);//加入右节点右孩子
       que.push(leftNode->right);//加入左节点右孩子
       que.push(rightNode->left);//加入右节点左孩子

    }
    return true;
   }
};

使用栈

把左右子树要比较的元素放进一个容器,然后成对的取出来进行比较,那么其实使用栈也是可以的。

只要把队列原封不动的改成栈就可以了

class Solution{
public:
   bool isSymmetric(TreeNode* root){
    if(root == NULL)return true;
    stack<TreeNode*>st;//这里改成了栈
    st.push(root->left);
    st.push(root->right);
    while(!st.empty()){
        TreeNode* leftNode = st.top();st.pop();
        TreeNode* rightNode = st.top();pop();
        if(!leftNode && !rightNode){
            continue;
        }
        if ((!leftNode || rightNode || (leftNode->val != rightNode -> val)))
        {
          return false;
        }
        st.push(leftNode->left);
        st.push(rightNode->right);
        st.push(leftNode->right);
        st.oush(rightNode->left);
    }
    return true;
   }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值