Day13.一刷数据结构算法(C语言版) 102二叉树的层序遍历;226翻转二叉树;101对称二叉树

devtools/2024/11/15 4:52:36/

一.102二叉树的层序遍历

        二叉树的层序遍历力扣题目

        1.思路分析

        这道题我没有什么好的思路,而且力扣给的函数形式看得有点懵,所以我找到一个相对好理解的题解,具体可以参考下方链接。 

        力扣题解

        说明:
        返回值:可以认为是一个动态分配的二维数组,行:树有几层就有几个int *指针,指向一个数组(存储了该层所有结点的值),列:每层结点的值
   int ** rslt = 【int *             | int *             | int *           | ...】       
                          ↓                     ↓                 ↓      ...         
              【int|int|...】       【int|int|...】    【int|int|...】    
 returnSize:用来返回二位数组的行树,即*returnSize = 有几层就赋值几
  returnColumnSizes:用来返回每层结点的个数,用一维数组来存储每层结点个数,这个数组要我们分配,调用者来free。

        不懂这个参数可以这样思考,如何要在函数内返回一个数组:
  1、通过返回值:
  int *p = f();
  int *f(void) {
     int *a = malloc(sizeof(int)*SIZE);
     return a;
  }
  2、通过参数:
  int *p = NULL;
  f(p);
  void f(int *a) {
     a = malloc(sizeof(int)*SIZE);
  }
  上面这样可以吗?当然不行,所以,要通过返回值返回一个在函数内分配的数组需要这样:
  int *p = NULL;
  f(&p);
  void f(int **a) {
     *a = malloc(sizeof(int)*SIZE);
  }

        2.代码详解

#define NODE_SIZE 10000
int** levelOrder(struct TreeNode* root, int* returnSize, int** returnColumnSizes){int **rslt = NULL;int *columnSize = NULL;struct TreeNode *queue[NODE_SIZE] = {0};                    /* 做队列使用 */struct TreeNode *pNode = NULL;int front = 0, rear = 0, pre_rear;int i = 0, j = 0;                                           /* i用来索引行,即层数,j用来索引列,即每层的结点个数 */if (!root) {*returnSize = 0;return NULL;}queue[rear++] = root;                                       /* 先把root结点入队 */while (front < rear) {                                      /* 外层循环:用来处理队列,队列不为空就循环处理 */pre_rear = rear;                                        /* 备份上一层的队尾指针 */rslt = realloc(rslt, (i + 1)*sizeof(int *));            /* 外层循环实际就是层数,每次扩充1 */rslt[i] = calloc(pre_rear - front, sizeof(int));while(front < pre_rear) {                               /* 内层循环:遍历每一层结点,每次出队一个结点,同时并把该结点的孩子入队 */pNode = queue[front++];                             /* 出队 */rslt[i][j++] = pNode->val;                          /* 存储结点值 */if (pNode->left)                                    /* 当前结点左、右孩子存在则将他们入队 */queue[rear++] = pNode->left;if (pNode->right)queue[rear++] = pNode->right;}columnSize = realloc(columnSize, (i + 1)*sizeof(int));  /* columnSize数组用来存储每层结点个数 */columnSize[i++] = j;j = 0;}*returnSize = i;                                            /* 如上注释,这个参数用来“带回”层数 */*returnColumnSizes = columnSize;                            /* 这个参数用来“带回”每层的结点个数 */return rslt;                                                /* 返回值存储了遍历的结果,上面两个参数用来描述这个结果,以便调用者打印树的形态 */
}

 二.226反转二叉树

        题目建议:这道题目 一些做过的同学 理解的也不够深入,建议大家先看我的视频讲解,无论做过没做过,都会有很大收获。

        题目链接/文章讲解/视频讲解:代码随想录

        1.思路分析

        想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。

        关键在于遍历顺序,前中后序应该选哪一种遍历顺序? (一些同学这道题都过了,但是不知道自己用的是什么顺序)

        遍历的过程中去翻转每一个节点的左右孩子就可以达到整体翻转的效果。

        注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果

        这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!建议拿纸画一画,就理解了

        那么层序遍历可以不可以呢?依然可以的!只要把每一个节点的左右孩子翻转一下的遍历方式都是可以的!

        我们下文以前序遍历为例,通过动画来看一下翻转的过程:

 

 

        我们来看一下递归三部曲:

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

         2.代码详解

struct TreeNode* invertTree(struct TreeNode* root){if(!root)return NULL;//交换结点的左右孩子(中)struct TreeNode* temp = root->right;root->right = root->left;root->left = temp;左invertTree(root->left);//右invertTree(root->right);return root;
}

三.101对称二叉树

        题目建议:先看视频讲解,会更容易一些。 

        题目链接/文章讲解/视频讲解:代码随想录

        1.思路分析

        首先想清楚,判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!

对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了其实我们要比较的是两个树(这两个树是根节点的左右子树),所以在递归遍历的过程中,也是要同时遍历两棵树。

        那么如何比较呢?

        比较的是两个子树的里侧和外侧的元素是否相等。如图所示:

        那么遍历的顺序应该是什么样的呢?

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

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

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

        其实后序也可以理解为是一种回溯,当然这是题外话,讲回溯的时候会重点讲的。

        说到这大家可能感觉我有点啰嗦,哪有这么多道理,上来就干就完事了。别急,我说的这些在下面的代码讲解中都有身影。

        那么我们先来看看递归法的代码应该怎么写。

        递归三部曲

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

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

        返回值自然是bool类型。

        代码如下:

bool compare(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,而是else if, 因为我们把以上情况都排除之后,剩下的就是 左右节点都不为空,且数值相同的情况。

        3)确定单层递归的逻辑

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

  • 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
  • 比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
  • 如果左右都对称就返回true ,有一侧不对称就返回false 。

        代码如下:

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

        如上代码中,我们可以看出使用的遍历方式,左子树左右中,右子树右左中,所以我把这个遍历顺序也称之为“后序遍历”(尽管不是严格的后序遍历)。 

        2.代码详解

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     struct TreeNode *left;*     struct TreeNode *right;* };*/
bool compare(struct TreeNode* left,struct 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 out=compare(left->left,right->right);bool in=compare(left->right,right->left);bool compareLeaf=in && out;return compareLeaf;
}bool isSymmetric(struct TreeNode* root) {if(root==NULL) return true;return compare(root->left,root->right);
}

         如果你有问题或者有其他想法,欢迎评论区留言,大家可以一起探讨。 


http://www.ppmy.cn/devtools/11002.html

相关文章

基本数据类型转换

一 自动类型转换 当java程序在进行赋值或者运算时&#xff0c;精度小的类型自动转换为精度大的数据类型 这个就是自动类型转换。 数据类型按精度(容量)大小排序 char -> int -> long -> float -> doublebyte -> short -> int -> long -> float -> …

spring.factories中配置ApplicationContextInitializer实现类却不起作用

自定义了一个ApplicationContextInitializer的实现类如下 public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {Overridepublic void initialize(ConfigurableApplicationContext applicationCon…

走出“至暗时刻”,托举三只松鼠的力量是什么?

品牌是时代的产物&#xff0c;也随时代而变化。能存在超过十年&#xff0c;依旧欣欣向荣的品牌&#xff0c;身上或多或少都承载了关于穿越周期的哲学。 最近的例子是零食行业的三只松鼠&#xff0c;三只松鼠的创业史&#xff0c;内涵非常丰富&#xff0c;有电商和实体的纠缠&a…

基于Springboot的心灵治愈交流平台

基于SpringbootVue的心灵治愈交流平台的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页展示 系统公告 心理咨询师 心灵专栏 压力测试 小纸条 后台登录界面 后…

linux18:进程等待

进程等待的必要性 1&#xff1a;子进程创建的目的是要完成父进程指派的某个任务&#xff0c;当子进程运行完毕退出时&#xff0c;父进程需要通过进程等待的方式&#xff0c;回收子进程资源&#xff0c;获取子进程退出信息&#xff08;子进程有无异常&#xff1f;没有异常结果是…

​Gu‘reum 工作室在The Sandbox推出 2024 年农历新年活动!

通过区块链游戏分享韩国文化并建立社区&#xff01; 去年 12 月&#xff0c;Gurenum 工作室 在The Sandbox 元宇宙上发起了 2023 年年末 Lan Party 直播活动。 https://sandboxgame.medium.com/gureum-studio-hosts-a-year-end-lan-party-in-the-sandbox-metaverse-b9a3fc6e7b9…

数据类型判断的方法

一、typeof 使用方法如下&#xff1a; typeof operand typeof(operand)operand表示要返回类型的对象或基本类型的表达式 &#xff0c;typeof运算符返回一个字符串&#xff0c;表示操作数的类型。 typeof 666 // number typeof 666 // string typeof undefined // undefined …

什么是Redis?

Redis 是一个开源的高性能键值存储数据库&#xff0c;它以内存中的数据结构服务器的形式存储数据&#xff0c;允许对这些数据进行读取和写入。Redis 是一个多用途的工具&#xff0c;被广泛用作缓存、消息队列、计数器、实时排行榜、会话存储和分布式锁等。 以下是 Redis 的一些…