C语言使用深度优先遍历(DFS)进行逆拓扑排序如何判断有向图是否含有环

文章目录

前言

本文采用邻接表法存储图,具体结构如下:

c 复制代码
typedef struct Edge {
    int weight;
    int vertexIndex;
    struct Edge *next;
} *Edge;

typedef struct Vertex {
    void *data;
    Edge firstEdge;
} *Vertex, *VertexList;

struct AdjacentListGraph {
    VertexList *vertexList;
    int vertexCount;
    int edgeCount;
    int size;

    int (*compare)(void *, void *);
};

详情请看用C语言实现所有图状结构及相关算法

深度优先遍历

深度优先遍历(DFS)的算法思想是:

  • 从图的某一顶点出发,访问它的任一邻接顶点;再从邻接顶点出发,访问邻接顶点的任一邻接顶点;如此往复直到访问到一个所有邻接顶点都被访问的顶点为止;
  • 接着回退一步,看看前一次访问的顶点是否还有其它没有被访问的邻接顶点;如果有,则访问此邻接顶点,之后再进行前述过程;如果没有,则再回退一步,重复上述过程,直到连通图中所顶点都被访问过为止。
c 复制代码
void DFS(AdjacentListGraph graph, int vertexIndex, bool isVisited[], LinkedQueue DFSDataQueue) {
    Vertex vertex = graph->vertexList[vertexIndex - 1];
    linkedQueueEnQueue(DFSDataQueue, vertex->data);
    isVisited[vertexIndex - 1] = true;
    for (Vertex j = firstVertex(graph, vertex->data); j != NULL; j = nextVertex(graph, vertex->data, j->data)) {
        int index = getVertexIndex(graph, j->data);
        if (!isVisited[index - 1]) {
            DFS(graph, index, isVisited, DFSDataQueue);
        }
    }
}

/**
 * 深度优先遍历
 * @param graph 图
 * @param data 起始结点
 * @param DFSDataQueue 用于保存遍历顶点
 */
void DFSTraverse(AdjacentListGraph graph, void *data, LinkedQueue DFSDataQueue) {
    bool *isVisited = calloc(graph->vertexCount, sizeof(bool));
    int index = getVertexIndex(graph, data);
    DFS(graph, index, isVisited, DFSDataQueue);
    for (int i = 1; i <= graph->vertexCount; ++i) {
        if (!isVisited[i - 1]) {
            DFS(graph, i, isVisited, DFSDataQueue);
        }
    }
}

深度优先遍历改造成逆拓扑排序

逆拓扑排序的算法思想如下:

  • 在AOV网中选一个没有后继的顶点,然后从图网中删除该顶点以及以这个顶点为终点的边。
  • 重复上面的步骤,直到网为空或网中不存在无后继的顶点为止。

没有后继的顶点就是在深度优先遍历递归函数出栈时的点,所以只需要在每次函数出栈时将当前顶点输出到遍历队列即可:

c 复制代码
void DFS(AdjacentListGraph graph, int index, bool isVisited[], LinkedQueue queue) {
    isVisited[index - 1] = true;
    Vertex vertex = graph->vertexList[index - 1];
    for (Edge edge = vertex->firstEdge; edge != NULL; edge = edge->next) {
        if (!isVisited[edge->vertexIndex - 1]) {
            DFSInTopological(graph, edge->vertexIndex, isVisited, queue);
        }
    }
    linkedQueueEnQueue(queue, vertex->data);
}

/**
 * 深度优先算法求逆拓扑排序
 * @param graph
 * @param queue
 */
void DFSInTopologicalSort(AdjacentListGraph graph, LinkedQueue queue) {
    bool *isVisited = calloc(graph->vertexCount, sizeof(bool));
    for (int i = 1; i <= graph->vertexCount; ++i) {
        if (!isVisited[i - 1]) {
            DFS(graph, i, isVisited, queue);
        }
    }
}

有环图问题

拓扑排序算法可以用于检测图中是否含有环:如果拓扑序列中含有所有图中的结点,那么该图就没有环,否则就含有环。但是使用DFS改造的算法即使传入的图有环,拓扑序列中也包含所有图中的顶点。那么就该解决这个问题,解决思想如下:

  • 如果一个图存在环,那么就意味着在DFS时访问到了一个已经访问的顶点并且该顶点对应的DFS函数还未出栈。
  • 我们在DFS中使用了一个布尔类型的isVisited数组来标记是否已经访问过该顶点,只需要将其改成一个整型数组,并且有以下值:
    • 0:表示未访问过
    • 1:表示已访问过
    • 2:已经访问过并且当前顶点对应的DFS函数还未出栈
  • 那么只需要在每次进入DFS函数时将该顶点的 isVisited数组的值设置为2,然后在该函数出栈时再设置为1,在函数运行的时候如果再次访问到了该顶点,只需要判断该顶点的isVisited数组是否是2就可以知道有没有环了。
c 复制代码
void DFS(AdjacentListGraph graph, int index, int isVisited[], LinkedQueue queue) {
    isVisited[index - 1] = 2;
    Vertex vertex = graph->vertexList[index - 1];
    for (Edge edge = vertex->firstEdge; edge != NULL; edge = edge->next) {
        if (isVisited[edge->vertexIndex - 1] == 0) {
            DFS(graph, edge->vertexIndex, isVisited, queue);
        }
        if (isVisited[edge->vertexIndex - 1] == 2) {
            throw Error(CYCLIC_GRAPH_ERROR, "图中含有环,逆拓扑排序失败");
        }
    }
    isVisited[index - 1] = 1;
    linkedQueueEnQueue(queue, vertex->data);
}

/**
 * 深度优先算法求逆拓扑排序
 * @param graph
 * @param queue
 */
void DFSInTopologicalSort(AdjacentListGraph graph, LinkedQueue queue) {
    int *isVisited = calloc(graph->vertexCount, sizeof(bool));
    for (int i = 1; i <= graph->vertexCount; ++i) {
        if (isVisited[i - 1] == 0) {
            DFS(graph, i, isVisited, queue);
        }
    }
}
相关推荐
LuminousCPP8 小时前
数据结构 - 线性表第四篇:C 语言通讯录优化升级全记录(踩坑 + 思考)
c语言·开发语言·数据结构·经验分享·笔记·学习
浩浩测试一下11 小时前
汇编 标志位寄存器 (逆向分析 )
c语言·汇编·逆向·windows编程·标志寄存器
SuperByteMaster12 小时前
uart中断发送和接收处理
c语言
一条大祥脚14 小时前
2021-2022 ICPC Southwestern Europe Regional Contest
算法·深度优先·图论
社交怪人14 小时前
【浮点数相除的余】信息学奥赛一本通C语言解法(题号1029)
c语言·开发语言
辣椒思密达14 小时前
Python爬虫中如何正确配置住宅IP代理?新手避坑指南
c语言·python
whuhewei14 小时前
React diff算法为什么是DFS,不是BFS
算法·react.js·深度优先
番茄灭世神15 小时前
PN学堂GD32教程第21篇——WiFiIOT
c语言·stm32·单片机·嵌入式·gd32
我还记得那天16 小时前
C语言递归实现汉诺塔问题
c语言·开发语言
LuminousCPP17 小时前
数据结构 - 线性表第三篇:基于顺序表实现 C 语言通讯录(基础功能篇)
c语言·数据结构·经验分享·笔记·算法