C语言经典算法之判断图中回路(深度优先遍历实现)

您所在的位置:网站首页 深度优先搜索遍历代码 C语言经典算法之判断图中回路(深度优先遍历实现)

C语言经典算法之判断图中回路(深度优先遍历实现)

2024-07-11 19:44| 来源: 网络整理| 查看: 265

目录

前言

A.建议

B.简介

一 代码实现

二 时空复杂度

A.时间复杂度分析

B.空间复杂度分析

三 优缺点

A.优点:

B.缺点:

四 现实中的应用

前言 A.建议

1.学习算法最重要的是理解算法的每一步,而不是记住算法。

2.建议读者学习算法的时候,自己手动一步一步地运行算法。

B.简介

判断一个图(这里特指有向图)是否存在回路,可以采用深度优先搜索(DFS)或拓扑排序两种方法。

一 代码实现

在深度优先搜索中,可以通过对每个节点进行标记来检测回路。遍历过程中将节点标记为“访问中”和“已访问”。如果在递归过程中遇到一个已经被标记为“访问中”的邻接节点,则说明存在环。

算法步骤:

初始化所有节点为未访问状态。对于每一个未访问节点u执行以下操作: 标记节点u为“访问中”。遍历节点u的所有邻接节点v: 如果节点v尚未被访问,则继续递归地进行深度优先搜索。如果节点v正处于“访问中”状态,则存在回路,返回真(true)。访问结束后,标记节点u为“已访问”。如果所有节点都被正确访问且没有发现回路,则图中不存在回路。

下面是一个简化的C语言伪代码框架:

typedef struct Node { int id; bool visited; // 是否访问过 bool in_process; // 是否正在访问中 struct Node* adj_list[ ]; // 邻接表表示法中的邻接节点指针数组 } Node; bool dfs_has_cycle(Node* node) { node->in_process = true; for (int i = 0; i < node->adj_count; ++i) { Node* neighbor = node->adj_list[i]; if (!neighbor->visited) { if (dfs_has_cycle(neighbor)) { return true; // 子树中有回路 } } else if (neighbor->in_process) { return true; // 发现相邻且正在访问中的节点,即回路 } } node->in_process = false; node->visited = true; return false; // 该子树无回路 } // 调用 bool has_cycle_in_graph() { for (each_node in graph) { if (!node->visited) { if (dfs_has_cycle(node)) { return true; } } } return false; }

请注意以上代码仅为示例性质,实际编写时需要根据具体的图数据结构(如邻接矩阵或邻接表)以及编程环境调整细节。

二 时空复杂度

A.时间复杂度分析

在给定的代码中,使用了深度优先搜索(DFS)来检测图中是否存在回路。时间复杂度主要取决于图中的边数和节点数。

对于每个节点,DFS函数都会遍历其所有邻接节点。假设每棵树的最大度数为 d(即最多有d 个邻接节点),整个图中共有 n 个节点,那么在最坏情况下,需要访问的边数最大为nd

由于 DFS 是递归执行的,且每个节点只会被访问一次,所以总的时间复杂度为 O(n*d),其中n是节点数,d是平均或最大度数。

然而,在实际应用中,如果图是高度稀疏的(比如边的数量远小于节点数量的平方),则实际上的时间复杂度可能会接近于节点数量 O(n)

B.空间复杂度分析

空间复杂度主要包括两部分:

存储图结构本身的空间:邻接表表示法中,每个节点存储了一个指向邻接节点数组的指针,数组长度等于节点的度数。因此,这部分的空间复杂度为 O(nd),但通常情况下我们关注的是常量因子,所以可以简化为O(n),因为每个节点都需要存储邻接信息。深度优先搜索时栈空间消耗:DFS 的递归调用会使用系统栈,当存在长链的情况下,所需栈空间与图的深度相关。在最坏情况下,如果图是一个链状结构,那么需要的栈空间为O(n)

综合考虑,忽略常量系数,该算法的空间复杂度大致为O(n)

三 优缺点

A.优点:

简洁性:代码采用深度优先搜索(DFS)策略,通过递归方式实现,逻辑清晰、易于理解。

高效检测回路:该算法在遍历过程中仅需对每个节点进行一次标记,即可判断是否存在环。当发现一个正在访问中的节点时立即返回结果,避免了不必要的额外检查。

空间效率:由于使用了“访问中”和“已访问”的状态标志,在非递归版本的实现中可以利用循环和栈来代替递归,从而减小系统栈的空间占用。

适用于多种图结构:该方法不仅适用于树形结构,也适用于更广泛的有向图结构,只要将邻接表表示法替换为对应的图数据结构即可。

B.缺点:

无法处理大规模图或高度密集图:如果图非常大或者边的数量非常多(尤其是接近完全图的情况),则时间复杂度较高,可能会导致性能问题。

不支持无环但具有自引用边的图:该代码假定自引用边(节点指向自身的边)不存在。如果存在自引用边,需要额外处理以防止误判为环。

递归深度限制:对于非常深的递归链(例如长链状结构),可能因为系统调用栈的限制而无法正确完成遍历和检测,此时需要考虑使用非递归版本的DFS或迭代加深的DFS。

侵入式设计:在Node结构体中添加了visited和in_process两个状态字段,这意味着这种方法是针对特定场景修改了原始数据结构,对于那些不能直接修改的数据结构可能不适用。

未处理异常情况:例如,没有对空图或无效输入进行错误检查,这在实际编程中是很重要的。

四 现实中的应用

软件工程和编程语言:

在编译器设计中,可以用来检查抽象语法树(AST)是否有循环引用,以确保语法正确性。程序优化时,对控制流图进行分析,判断是否存在死循环。

操作系统与并发编程:

在进程调度算法中,通过检测任务依赖关系图来判断是否存在循环等待条件,避免出现死锁现象。对于分布式系统,检测资源分配图是否存在环路,防止循环引用导致的资源无法回收问题。

数据结构和算法:

检查有向无环图(DAG)是否满足拓扑排序的条件。在图遍历、路径查找、网络路由算法等场景下,都需要先确认图是否含有环路。

社交网络和推荐系统:

社交网络中检测用户的好友关系是否存在闭环(例如A关注了B,B关注了C,C又关注了A),有助于分析社区结构和传播效果。推荐系统中,在构建基于用户的兴趣关联网络时,避免因环路造成无限递归推荐同一批内容。

计算机图形学和游戏开发:

在处理场景图或者物体层级关系时,确保不存在循环引用,以避免渲染错误或逻辑混乱。

数据库和数据挖掘:

检测数据库表之间的外键关联是否存在循环,保证数据的一致性和完整性。数据挖掘过程中,分析多维数据集中的关联规则时,需要排除含有环路的规则。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3