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);
        }
    }
}
相关推荐
啟明起鸣3 分钟前
【Nginx 网关开发】上手 Nginx,简简单单启动一个静态 html 页面
运维·c语言·前端·nginx·html
卡里笔记8 分钟前
C语言版2048小游戏
c语言
梵刹古音23 分钟前
【C语言】 循环结构
c语言·开发语言·算法
皮皮哎哟30 分钟前
冒泡排序与数组传递全解析 一维二维指针数组及二级指针应用指南
c语言·算法·冒泡排序·二维数组·指针数组·传参·二级指针
蒸蒸yyyyzwd33 分钟前
c网络编程学习笔记
c语言·网络·学习
烟花落o42 分钟前
贪吃蛇及相关知识点讲解
c语言·前端·游戏开发·贪吃蛇·编程学习
头发还没掉光光42 分钟前
Linux 高级 IO 深度解析:从 IO 本质到 epoll全面讲解
linux·服务器·c语言·c++
燃于AC之乐1 小时前
《算法实战笔记》第10期:六大算法实战——枚举、贪心、并查集、Kruskal、双指针、区间DP
算法·贪心算法·图论·双指针·区间dp·二进制枚举
No0d1es1 小时前
电子学会青少年软件编程(C语言)等级考试试卷(一级)2025年12月
c语言·青少年编程·等级考试·电子学会·一级
爱编码的小八嘎1 小时前
C语言对话-18.我为你准备一切
c语言