当前位置: 首页 > news >正文

310.力扣LeetCode_ 最小高度树_直径法_DFS

  • 思路2:直径法

最长的链子一定存在。其它的链子可以看做是和最长链子相交的,类似字母“X”型或者“Y”型。任取一个节点,距离其最远的节点一定是最长链子的某个端点,记为u。再找到距离u最远的点,就一定是最长链子的另一个端点v了。我们找到了最长链子的首尾两端,也就是找到了这个图的“直径”,由于最长链子节点数量的奇偶性,其中心位置可能是一个或者两个节点

  • DFS+直径法代码实现

typedef struct ListNode ListNode;ListNode** create_adj(int** edges,int edgesSize,int n){ListNode** adj=(ListNode **)calloc(n,sizeof(ListNode *));for(int i=0;i<edgesSize;i++){int x=edges[i][0];int y=edges[i][1];ListNode *y_node=(ListNode *)malloc(sizeof(ListNode));y_node->val=x;y_node->next=adj[y];adj[y]=y_node;ListNode *x_node=(ListNode *)malloc(sizeof(ListNode));x_node->val=y;x_node->next=adj[x];adj[x]=x_node;}return adj;
}void dfs(int cur, int distance[], int parent[],ListNode ** adj) {ListNode* p=adj[cur];while(p){int cur_neighbor=p->val;if(distance[cur_neighbor]<0){distance[cur_neighbor]=distance[cur]+1;parent[cur_neighbor]=cur;dfs(cur_neighbor,distance,parent,adj);}p=p->next;}
}int findLongestNode(int cur, int parent[], ListNode ** adj, int n) {int distance[n];memset(distance,-1,n*sizeof(int));distance[cur]=0;dfs(cur,distance,parent,adj);int distance_max=0,ans=-1;for(int i=0;i<n;i++){if(distance[i]>distance_max){distance_max=distance[i];ans=i;}}return ans;
}int get_path(int path[],int parent[],int x,int y){int path_length = 0;parent[x] = -1;while (y != -1) {path[path_length++] = y;y = parent[y];}return path_length;
}int* findMinHeightTrees(int n, int** edges, int edgesSize, int* edgesColSize, int* returnSize){int * res = NULL;if (n == 1) {res = (int *)malloc(sizeof(int));res[0] = 0;*returnSize = 1;return res;}ListNode** adj=create_adj(edges,edgesSize,n);int * parent = (int *)malloc(sizeof(int) * n);int x = findLongestNode(0, parent, adj, n);int y = findLongestNode(x, parent, adj, n);int * path = (int *)malloc(sizeof(int) * n);int path_length=get_path(path,parent,x,y);if (path_length % 2 == 0) {res = (int *)malloc(sizeof(int) * 2);res[0]=path[path_length/2-1];res[1]=path[path_length/2];*returnSize = 2;} else {res = (int *)malloc(sizeof(int));*res = path[path_length/ 2];*returnSize = 1;}free(path);free(parent);for (int i = 0; i < n; i++) {ListNode * p= adj[i];while (p) {ListNode * temp = p;p = p->next;free(temp);}}free(adj);return res;
}

  • 代码详细解析

  • 函数1:头插法建立链式邻接表

ListNode** create_adj(int** edges,int edgesSize,int n){ListNode** adj=(ListNode **)calloc(n,sizeof(ListNode *));for(int i=0;i<edgesSize;i++){int x=edges[i][0];int y=edges[i][1];ListNode *y_node=(ListNode *)malloc(sizeof(ListNode));y_node->val=x;y_node->next=adj[y];adj[y]=y_node;ListNode *x_node=(ListNode *)malloc(sizeof(ListNode));x_node->val=y;x_node->next=adj[x];adj[x]=x_node;}return adj;
}

用calloc给链式邻接表adj分配空间,可以顺便将每行的头指针初始化。否则还需要额外写循环或者使用memset进行初始化

核心的构建过程为头插法。由于是双向边,所以需要分别对节点x和y对应的链表进行插入操作。以对adj[y]进行插入为例,先创建一个新的节点y_node,将其值赋为x,表示x是y的邻居节点,然后将y_node指向头结点adj[y],然后再将adj[y]赋值为y_node,相当于让adj[y]重新指向链表头结点


  • 函数2:dfs深度优先搜索

void dfs(int cur, int distance[], int parent[],ListNode ** adj) {ListNode* p=adj[cur];while(p) {int cur_neighbor = p->val;if (distance[cur_neighbor] < 0) {distance[cur_neighbor] = distance[cur] + 1;parent[cur_neighbor] = cur;dfs(cur_neighbor, distance, parent, adj); }p=p->next;}
}

在一次深度优先搜索中,我们需要一边遍历,一边对两个辅助数组对进行创建,分别是distance和parent

在常规的深度优先搜索中,为了防止重复访问,我们需要借助visited数组,来标记那些已访问过的节点。但是在这个算法中,distance数组恰好可以完成visited数组标记结点任务,就无需再额外引入visited数组了。

distance数组在传入dfs函数时,已经被全部初始化为-1,只有当前节点distance[cur]被赋值为0。我们在这里通过distance数组记录的数值的正负来区分哪些节点被访问过,而哪些节点还未被访问

因为后续还需要获取两个节点之间的路径path,因此我们还记录了parent数组


  • 函数3:findLongestNode

int findLongestNode(int cur, int parent[], ListNode ** adj, int n) {int distance[n];memset(distance,-1,n*sizeof(int));distance[cur]=0;dfs(cur,distance,parent,adj);int distance_max=0,ans=-1;for(int i=0;i<n;i++){if(distance[i]>distance_max){distance_max=distance[i];ans=i;}}return ans;
}

这个函数的主要功能就是通过调用dfs,找到距离cur最远的结点。

distance数组不用返回给上层函数,在本层内调用,直接在栈空间申请即可。在调用dfs之前,需要对distance数组进行初始化,先将所有的值全部赋为-1,然后再单独将开始位置的distance[cur]设置为0。在dfs中distance有着双重身份,首先根据存入值的正负,用来区分哪些节点还未被访问,扮演visited数组的角色,同时还记录着距离信息,用于后续找到最远节点,因此在传入dfs之前进行初始化尤为重要

调用dfs后,我们需要找到距离最远的节点,然后将其下标返回给上层函数


  • 函数4:get_path

int get_path(int path[],int parent[],int x,int y){int path_length=0;parent[x]=-1;while(y!=-1){path[path_length++]=y;y=parent[y];}return path_length;
}

这个函数的主要作用是,通过parent数组来生成从x到y的路径,并且返回路径的长度。这里有一个非常重要的细节

parent[x]赋值为-1是一个极其重要的步骤。首先,我们在最后的主函数中创建parent数组时,并没有进行初始化的操作,然后我们一共调用了两次findlongest函数,每次调用findlongest都会调用一次dfs函数,而只有在dfs函数中,parent数组的值会被修改。接下来就是最关键的一点,dfs 不会对parent[cur]进行任何修改,除了parent[cur]的其他n-1个元素都进行了修改。

我们在两次调用findlongest函数之间也并未对parent数组进行任何重置,因此,最后传入的get_path函数中的parent[x],相当于记录了是0号节点到x的路径中,x的父亲节点

因此,在生成path时,没有对parent[x]进行重新赋值为-1,会导致y的循环条件错误,相当于是被之前的“脏数据”影响了

int get_path(int path[],int parent[],int x,int y){int path_length=0;while(y!=parent[x]){path[path_length++]=y;y=parent[y];}return path_length;
}

更详细的解释:如果按照上面的函数进行执行,此时记录中的parent[x]可能在x与y之间,导致记录的距离路径长度缩短,得到错误的结果


  • 主函数:findMinHeightTrees

  • 边界处理

    int * res = NULL;if (n == 1) {res = (int *)malloc(sizeof(int));res[0] = 0;*returnSize = 1;return res;}

我们需要对节点数为1的特殊场景进行边界处理。因为我们接下来需要调用两次findlongestNode

int x = findLongestNode(0, parent, adj, n);

int y = findLongestNode(x, parent, adj, n);

当只有一个节点时,会得到x=-1,接下来再将x传入findlongest就会报错了


  • 函数调用

    ListNode** adj=create_adj(edges,edgesSize,n);int * parent = (int *)malloc(sizeof(int) * n);int x = findLongestNode(0, parent, adj, n);int y = findLongestNode(x, parent, adj, n);int * path = (int *)malloc(sizeof(int) * n);int path_length=get_path(path,parent,x,y);

这一段主要是函数的调用。

创建链式邻接表adj。

创建parent数组,然后两次调用findlongestNode函数。这也是直径法的核心思想。先选取一个起始节点0,找到距离它最远的节点x,然后再调用函数找到距离节点x最远的结点y,这样x到y的路径就是这棵树的直径

创建路径数组,调用get_path


  • 结果统计——奇偶性分析

    if (path_length % 2 == 0) {res = (int *)malloc(sizeof(int) * 2);res[0]=path[path_length/2-1];res[1]=path[path_length/2];*returnSize = 2;} else {res = (int *)malloc(sizeof(int));*res = path[path_length/ 2];*returnSize = 1;}

我们根据路径长度的奇偶性,就可以判断最终符合条件的节点个数。

如果路径长度为偶数,那么路径最中间的两个节点都符合要求。如果路径长度为奇数,那么只有最中间的一个节点符合要求


  • 内存释放

    free(path);free(parent);for (int i = 0; i < n; i++) {ListNode * p= adj[i];while (p) {ListNode * temp = p;p = p->next;free(temp);}}free(adj);

我们需要释放两个一维数组,path和parent。还需要释放链式邻接表adj。adj的释放需要双层循环。最后再释放二级指针adj


  • 注释版本

// 定义邻接表节点结构(需确保结构体完整定义,此处为前向声明)
typedef struct ListNode ListNode;
struct ListNode {int val;               // 存储邻接节点的索引struct ListNode *next; // 指向下一个邻接节点的指针
};/*** @brief 构建无向图的链式邻接表* @param edges 边的二维数组,edges[i] = {x, y} 表示节点x与y之间有边* @param edgesSize 边的总数* @param n 图中节点的总数(节点索引从0开始)* @return ListNode** 构建完成的链式邻接表,adj[i]表示节点i的邻接链表头指针*/
ListNode** create_adj(int** edges, int edgesSize, int n) {// 用calloc分配n个ListNode*类型的空间,初始值均为NULL(避免野指针)ListNode** adj = (ListNode **)calloc(n, sizeof(ListNode *));// 遍历所有边,为每个边构建双向邻接关系(无向图)for (int i = 0; i < edgesSize; i++) {int x = edges[i][0]; // 边的一个节点int y = edges[i][1]; // 边的另一个节点// 1. 为节点y的邻接链表插入节点xListNode *y_node = (ListNode *)malloc(sizeof(ListNode)); // 分配新节点y_node->val = x;                                         // 新节点存储x的索引y_node->next = adj[y];                                   // 新节点指向原邻接链表头部adj[y] = y_node;                                         // 更新邻接链表头部为新节点(头插法)// 2. 为节点x的邻接链表插入节点y(对称操作,保证无向性)ListNode *x_node = (ListNode *)malloc(sizeof(ListNode));x_node->val = y;x_node->next = adj[x];adj[x] = x_node;}return adj; // 返回构建好的邻接表
}/*** @brief 深度优先搜索(DFS),遍历图并记录节点距离与父节点* @param cur 当前遍历的节点索引* @param distance 距离数组:distance[i]表示起始节点到i的距离,-1表示未访问* @param parent 父节点数组:parent[i]表示遍历中i的前驱节点(用于后续路径追溯)* @param adj 链式邻接表,存储图的结构*/
void dfs(int cur, int distance[], int parent[], ListNode **adj) {ListNode* p = adj[cur]; // 获取当前节点的邻接链表头部// 遍历当前节点的所有邻接节点while (p) {int cur_neighbor = p->val; // 邻接节点的索引// 若邻接节点未访问(distance为-1)if (distance[cur_neighbor] < 0) {distance[cur_neighbor] = distance[cur] + 1; // 更新距离:当前节点距离+1parent[cur_neighbor] = cur;                 // 记录父节点:邻接节点的父节点是当前节点dfs(cur_neighbor, distance, parent, adj);   // 递归遍历邻接节点(深度优先)}p = p->next; // 移动到下一个邻接节点}
}/*** @brief 找到从指定起始节点出发,距离最远的节点* @param cur 起始节点索引* @param parent 父节点数组(用于存储DFS中的节点前驱关系,由DFS修改)* @param adj 链式邻接表* @param n 节点总数* @return int 距离起始节点最远的节点索引*/
int findLongestNode(int cur, int parent[], ListNode **adj, int n) {int distance[n]; // 距离数组,存储起始节点到各节点的距离// 用memset将distance数组全部初始化为-1(标记未访问)memset(distance, -1, n * sizeof(int));distance[cur] = 0; // 起始节点到自身的距离为0dfs(cur, distance, parent, adj); // 调用DFS填充距离数组和父节点数组int distance_max = 0; // 记录最大距离int ans = -1;         // 记录距离最远的节点索引// 遍历所有节点,找到距离最大的节点for (int i = 0; i < n; i++) {if (distance[i] > distance_max) {distance_max = distance[i]; // 更新最大距离ans = i;                    // 更新最远节点索引}}return ans; // 返回最远节点索引
}/*** @brief 基于父节点数组,追溯从节点x到节点y的路径,并记录路径长度* @param path 路径数组:存储追溯得到的路径(从y到x的反向路径)* @param parent 父节点数组:记录节点的前驱关系(由两次findLongestNode填充)* @param x 路径的起点(树直径的一个端点)* @param y 路径的终点(树直径的另一个端点)* @return int 路径的节点总数(路径长度)*/
int get_path(int path[], int parent[], int x, int y) {int path_length = 0; // 记录路径的节点总数// 关键:将x的父节点设为-1,作为路径追溯的终止标记(避免脏数据干扰)parent[x] = -1;// 从y反向追溯到x(直到y为-1,即追溯到x)while (y != -1) {path[path_length++] = y; // 将当前节点y存入路径数组,路径长度+1y = parent[y];           // 移动到y的父节点(向x方向追溯)}return path_length; // 返回路径的节点总数
}/*** @brief 求解无向树的最小高度树(MHT)的根节点* @param n 节点总数* @param edges 边的二维数组* @param edgesSize 边的总数* @param edgesColSize 每个边数组的长度(此处无用,兼容接口)* @param returnSize 输出参数:返回结果数组的长度(1或2)* @return int* 最小高度树的根节点数组(1个或2个元素)*/
int* findMinHeightTrees(int n, int** edges, int edgesSize, int* edgesColSize, int* returnSize) {int *res = NULL; // 存储结果的数组(最小高度树的根节点)// 边界条件:只有1个节点时,该节点就是唯一的最小高度树根if (n == 1) {res = (int *)malloc(sizeof(int)); // 分配1个int的空间res[0] = 0;                       // 唯一节点索引为0*returnSize = 1;                  // 结果数组长度为1return res;                       // 直接返回结果,避免后续无效操作}// 1. 构建链式邻接表ListNode** adj = create_adj(edges, edgesSize, n);// 2. 分配父节点数组(存储节点前驱关系,用于路径追溯)int *parent = (int *)malloc(sizeof(int) * n);// 3. 两次调用findLongestNode,求解树的直径(x和y是直径的两个端点)int x = findLongestNode(0, parent, adj, n);  // 第一步:从0出发找最远节点xint y = findLongestNode(x, parent, adj, n);  // 第二步:从x出发找最远节点y(x-y为树直径)// 4. 分配路径数组,追溯x到y的直径路径,并获取路径长度int *path = (int *)malloc(sizeof(int) * n);int path_length = get_path(path, parent, x, y);// 5. 根据路径长度的奇偶性,确定最小高度树的根节点(直径的中点)if (path_length % 2 == 0) {// 偶数长度:2个中点(根节点)res = (int *)malloc(sizeof(int) * 2);res[0] = path[path_length / 2 - 1]; // 前一个中点(反向路径的索引)res[1] = path[path_length / 2];     // 后一个中点*returnSize = 2;                    // 结果数组长度为2} else {// 奇数长度:1个中点(根节点)res = (int *)malloc(sizeof(int));*res = path[path_length / 2];       // 唯一中点(反向路径的中间索引)*returnSize = 1;                    // 结果数组长度为1}// 6. 释放动态分配的内存(避免内存泄漏)free(path);   // 释放路径数组free(parent); // 释放父节点数组// 释放邻接表:先释放每个链表的节点,再释放邻接表数组for (int i = 0; i < n; i++) {ListNode *p = adj[i]; // 获取第i个节点的邻接链表头部while (p) {ListNode *temp = p; // 临时保存当前节点(避免释放后丢失后续节点)p = p->next;        // 移动到下一个节点free(temp);         // 释放当前节点}}free(adj); // 释放邻接表数组return res; // 返回最小高度树的根节点数组
}
http://www.dtcms.com/a/553591.html

相关文章:

  • 建设电影网站选服务器怎么选贵州安顺做公司网站
  • 小城镇建设的网站中的主要观点个人域名备案查询
  • 互联网站管理工作细则网页qq属于
  • 润商网站建设服务河北住房和城乡建设厅网站驱动
  • 【轨物方案】智控未来,运维无忧——操作机构机械特性物联网软硬件一站式解决方案
  • 【安科瑞解读】母线槽监控:从“盲管”到“智能运维”的跨越式升级
  • 使用goland ide工具,本地调试运行main.go报错,无法断点调试
  • 仓颉语言三方库开发与使用指南(通用仓颉篇)
  • 【互联网产品助理的成长之路(1)】需求设计的大致流程及思考
  • JAVASE速通复习(二)
  • 广州做网站好的公司胶州网站建设 网络推广
  • git提交 关键字介绍 约定式提交
  • vue2 vue3 修改elementUI和elementPlus主题颜色
  • 易班网站建设基础贵阳设计网站建设
  • 【Linux】Linux 权限
  • 食品电子商务网站建设规划书一般网站的宽度是多少
  • Python爬虫实战:美元-人民币汇率历史数据获取与趋势分析
  • android studio创建使用开发打包教程
  • 基于Springboot的影视评论网站的设计与实现58py6238(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
  • 57套思维导图PPT模板合集 培训/头脑风暴/工作规划 可编辑PPTX
  • 项目源码安全审查—密钥硬编码问题
  • 内蒙古城乡住房建设厅网站网站建设及发布的流程
  • Java 大视界 -- Java 大数据机器学习模型在遥感图像土地利用分类中的优化与应用
  • Hutool 全面实战指南:让 Java 开发更“甜”
  • LangChain进阶学习
  • 网站广告图怎么做网站开发的工作制度
  • 关于Delphi的一次吵架
  • 全连接层的第二层是怎么减少神经节点数
  • Rust开发之泛型约束与where子句的应用
  • 什么是CVE?如何通过SAST/静态分析工具Perforce QAC 和 Klocwork应对CVE?