判断有向图中是否存在环

本文介绍了一种基于邻接矩阵的图算法,用于检测有向图中的环路。通过递增跳数来构建节点可达性集合,并利用该集合判断是否存在环路。文章提供了完整的源代码实现及运行结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

方法概要:

nodeset_dst[i][k][ ]表示i节点经过K跳可达节点集合;

步骤1:从0跳(节点自身)开始根据邻接矩阵逐跳添加节点:即如果节点i经过k-1跳可达节点m,且m存在到n的有向边;则i经过k跳可达n(注意处理i到n的其他更短路径),将n添加到nodeset_dst[i][k][ ]数组中;并将nodeset_map[i][n]低4bit置1,表示n在i的可达目的节点集合中,nodeset_map[n][i]高4bit置1,表示i在可达n的源点集合中

步骤2:按步骤1循环处理每一个节点,在为节点i添加可达目的节点j时,如果发现j位于可达节点i的源节点集合中,则找到环。



源代码:

/*check whether there is a cycle in graph*/

#include <stdio.h>
#include <string.h>
#define TOTAL_NODE_NUMBER 6

char adj_matrix[TOTAL_NODE_NUMBER][TOTAL_NODE_NUMBER] = {
    1,0,0,0,0,0,
    0,1,0,0,0,1,
    0,1,1,0,0,0,
    1,0,0,1,0,0,
    1,0,0,0,1,0,
    0,0,1,1,1,1
};

/*nodeset_dst[i][k][]表示i节点经过K跳可达节点集合
 *nodeset_dst_cnt[i][k]记录节点i从发经过k跳可达节点的个数
 *nodeset_map[i][j]; 0-3bit非0表示节点j在节点i的可达目的节点集合中,4-7bit非0表示节点j在可达节点i的源节点集合中。
 *shortest_path[i][j][]记录从i到j经过的最短路径*/
char nodeset_dst[TOTAL_NODE_NUMBER][TOTAL_NODE_NUMBER][TOTAL_NODE_NUMBER];
char nodeset_dst_cnt[TOTAL_NODE_NUMBER][TOTAL_NODE_NUMBER-1];
char nodeset_map[TOTAL_NODE_NUMBER][TOTAL_NODE_NUMBER];

/*shortest_path[i][j].node_set表示从i到j的路径包含到节点,含i,j
 *shortest_path[i][j].node_cnt表示从i到j到路径包含的节点个数,含i,j*/
typedef struct stPath {
    char node_cnt;
    char node_set[TOTAL_NODE_NUMBER];
} stPath;
stPath shortest_path[TOTAL_NODE_NUMBER][TOTAL_NODE_NUMBER];

void add_to_shortest_path(char firstnode, char lastnode, char newnode) {
    char tmp_cnt, i;
    
    tmp_cnt = shortest_path[firstnode][lastnode].node_cnt;
    for(i = 0; i < tmp_cnt; i++) {
        shortest_path[firstnode][newnode].node_set[i] = shortest_path[firstnode][lastnode].node_set[i];
    }   
    shortest_path[firstnode][newnode].node_set[i] = newnode;
    shortest_path[firstnode][newnode].node_cnt = i + 1;
}

void graph_init() {
    int node_index;

    memset(nodeset_dst, -1, sizeof(nodeset_dst));
    memset(nodeset_dst_cnt, 0, sizeof(nodeset_dst_cnt));
    memset(nodeset_map, 0, sizeof(nodeset_map));
    memset(shortest_path, -1, sizeof(shortest_path));

    for(node_index = 0; node_index < TOTAL_NODE_NUMBER; node_index ++) {
        nodeset_dst[node_index][0][0] = node_index;
        nodeset_dst_cnt[node_index][0] = 1;
        nodeset_map[node_index][node_index] |= 0x11;
        shortest_path[node_index][node_index].node_set[0] = node_index;
        shortest_path[node_index][node_index].node_cnt = 1;
    }   
}

void print_cycle(int src, int dst) {
    int i;

    printf("cycle is :\n");
    for(i = 0; i < shortest_path[src][dst].node_cnt; i++) {
        printf ("%d ", shortest_path[src][dst].node_set[i]);
    }   

    for(i = 1; i < shortest_path[dst][src].node_cnt; i++) {
        printf ("%d ", shortest_path[dst][src].node_set[i]);
    }   
    printf("\n");
}

void main() {
    int node_index, jump, i, j;
    int last_node, node_cnt;

    graph_init();
    for(node_index = 0; node_index < TOTAL_NODE_NUMBER; node_index ++) {  //遍历图中每个节点
        for(jump = 1; ; jump ++){//遍历从一个节点A出发的跳数
            for(i = 0; i < nodeset_dst_cnt[node_index][jump-1]; i ++) {//遍历到节点A距离jump-1跳的节点
                last_node = nodeset_dst[node_index][jump -1][i];
                for(j = 0; j < TOTAL_NODE_NUMBER; j ++) {//以到节点A距离jump-1跳的节点b作为起始地点查找jump跳的节点
                    if((last_node == j) || ((nodeset_map[node_index][j] & 0x0f) != 0)){
                        continue;
                    } else if(adj_matrix[last_node][j] != 0) {
                        //将节点j加入到node_index的jump跳可达节点中
                        add_to_shortest_path(node_index, last_node, j);
                        //printf("node:%d, jump:%d, lastnode:%d, newnode:%d\n",node_index, jump, last_node, j);
                        node_cnt = nodeset_dst_cnt[node_index][jump];
                        nodeset_dst[node_index][jump][node_cnt] = j;
                        nodeset_dst_cnt[node_index][jump] ++;
                        nodeset_map[node_index][j] |= 0x01;
                        nodeset_map[j][node_index] |= 0x10;

                        if((nodeset_map[node_index][j] & 0xf0) != 0){
                            //找到环了,打印shortest_path中的路径
                            print_cycle(node_index, j);
                            return;
                        }
                    }
                }
            }
            if (nodeset_dst_cnt[node_index][jump] == 0)
                break;
        }
    }   
}


程序执行结果:

fhy@fhy-laptop:~/wkspc/interesting_puzzle$ gcc cycle_check_in_graph.c
fhy@fhy-laptop:~/wkspc/interesting_puzzle$ ./a.out
cycle is :
2 1 5 2

<think>好的,我现在需要解决用户的问题:如何判断有向图是否存在。根据用户提供的引用内容,我需要整理相关方法,并结合自己的知识来回答。首先,我应该先理解用户提供的引用内容,看看有哪些方法已经被提到。 引用[1]提到,无向图检测可以通过检查邻居是否已访问且不是父节点,但有向图不能这样做,因为边的方向性会影响判断。比如用户提到的例子,直接使用类似无向图的方法会误判。所以有向图需要不同的方法。 引用[2]和引用[4]都提到了拓扑排序的方法。拓扑排序的核心是不断删除入度为0的节点,直到无法删除为止。如果最终还有节点未被删除,说明存在。这个方法我应该详细说明步骤,比如如何计算入度,维护队列等。 引用[3]和引用[5]可能涉及DFS的方法,但用户的具体引用内容没有详细说明,不过根据常见算法,DFS在有向图中检测通常需要标记节点的访问状态,比如使用三种状态:未访问、访问中、已访问。当DFS遍历时遇到正在访问中的节点,说明存在。这个方法需要详细解释状态标记的过程。 引用[5]的例子是关于课程安排的,这正是拓扑排序的经典应用,比如LeetCode的课程表问题。所以用户可能需要实际应用的例子。 现在,我需要将这两种方法——拓扑排序和DFS深度优先搜索——分别解释清楚。确保步骤正确,并给出代码示例或伪代码。同时,需要注意用户提到的LaTeX格式要求,比如行内公式用$...$,独立公式用$$...$$,但根据用户的问题,可能数学公式较少,但如果有涉及公式的地方需要遵守格式。 另外,用户要求生成相关问题,我需要根据内容提出几个相关问题,比如两种方法的区别、时间复杂度、应用场景等。 现在,整理回答的结构: 1. 方法一:拓扑排序(Kahn算法) - 步骤:计算入度,队列初始化,处理节点,判断剩余节点。 - 代码示例可能用Python,比如维护入度表和邻接表,使用队列进行BFS。 - 引用中的例子,比如调度引擎检测循依赖,课程安排问题。 2. 方法二:DFS检测回溯路径 - 状态标记:0未访问,1访问中,2已访问。 - DFS过程中,如果遇到状态为1的节点,说明有。 - 代码示例,递归实现DFS,更新状态。 3. 对比两种方法的时间复杂度,适用场景。 - 拓扑排序适合需要排序结果的情况,或者需要迭代处理的情况。 - DFS可能在特定情况下更快,但递归深度可能受限。 然后,检查是否有需要补充的细节,比如两种方法的具体实现步骤是否正确,是否有常见的错误需要避免。例如,在拓扑排序中,需要确保每次减少入度后检查是否入度为0,并及时加入队列。在DFS中,访问中的状态必须正确标记,避免重复访问。 接下来,代码示例需要符合用户的格式要求,用三个反引号包裹,并注明语言。比如Python代码实现拓扑排序和DFS方法。 最后,生成相关问题,考虑用户可能想进一步了解两种方法的差异、优化、应用实例等。 在写作过程中,要确保中文回答,行内公式正确,比如如果有涉及公式的地方,比如复杂度分析中的O(n)用$O(n)$表示。但根据当前问题,可能公式较少,但需要注意如果有的话要正确使用格式。 现在开始组织回答内容,确保结构清晰,分点说明,并正确引用提供的参考资料,比如在提到拓扑排序时引用[2][4][5],提到DFS时引用[1][3]。</think>### 判断有向图是否存在的方法 #### 一、拓扑排序法(Kahn算法) **核心思想**:若图中存在,则无法生成完整的拓扑序列,因为内节点的依赖关系无法被消除[^2][^4]。 **实现步骤**: 1. **构建入度表与邻接表**:记录每个节点的入度,并存储每个节点的出边。 2. **初始化队列**:将所有入度为0的节点加入队列。 3. **迭代处理**: - 取出队列中的节点,加入拓扑序列。 - 遍历该节点的邻居,将邻居的入度减1。若邻居入度为0,则加入队列。 4. **判断剩余节点**:若最终处理的节点数等于总节点数,则无;否则存在。 **代码示例**: ```python def has_cycle(num_nodes, edges): from collections import deque in_degree = [0] * num_nodes adj = [[] for _ in range(num_nodes)] for u, v in edges: adj[u].append(v) in_degree[v] += 1 queue = deque([i for i in range(num_nodes) if in_degree[i] == 0]) count = 0 while queue: node = queue.popleft() count += 1 for neighbor in adj[node]: in_degree[neighbor] -= 1 if in_degree[neighbor] == 0: queue.append(neighbor) return count != num_nodes ``` **应用场景**:课程安排(如LeetCode 207题)、任务调度引擎检测循依赖[^5][^2]。 --- #### 二、DFS深度优先搜索法 **核心思想**:通过回溯遍历路径,若发现某节点在递归栈中重复出现,则存在[^1][^3]。 **实现步骤**: 1. **标记节点状态**: - `0`:未访问 - `1`:访问中(当前递归栈中) - `2`:已访问 2. **递归遍历**: - 若遇到状态为`1`的邻居,说明存在。 - 遍历结束后将节点标记为`2`。 **代码示例**: ```python def has_cycle(num_nodes, edges): adj = [[] for _ in range(num_nodes)] for u, v in edges: adj[u].append(v) visited = [0] * num_nodes # 0-未访问, 1-访问中, 2-已访问 def dfs(node): if visited[node] == 1: return True if visited[node] == 2: return False visited[node] = 1 for neighbor in adj[node]: if dfs(neighbor): return True visited[node] = 2 return False for i in range(num_nodes): if dfs(i): return True return False ``` **注意事项**:需遍历所有连通分量,避免漏检未连接的[^3]。 --- #### 三、两种方法对比 | **方法** | **时间复杂度** | **适用场景** | |----------------|---------------|--------------------------------| | 拓扑排序(Kahn) | $O(V+E)$ | 需要输出拓扑序列或处理节点依赖关系 | | DFS | $O(V+E)$ | 快速检测,无需额外维护队列 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值