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);
        }
    }
}
相关推荐
是隼人4 小时前
buuctf-pwn [HarekazeCTF2019]baby_rop2(64位ret2libc)题解(学习过程持续更新)
c语言·学习·安全·pwn入门·ctf入门
wuyk5556 小时前
107.FreeRTOS 链表深度解析:从原理到面试满分答案
c语言·开发语言·数据结构·stm32·单片机·链表·面试
AAA代码批发商8 小时前
Days 39 Linux C 开发之 SQLite 数据库完整学习笔记
linux·c语言·数据库
卢锡荣9 小时前
单芯掌控多口互联|乐得瑞 LDR6020 PD3.1 多通道 Type‑C 控制 SOC 芯片
c语言·开发语言
wuminyu9 小时前
JVM锁膨胀与Futex源码解析
java·linux·c语言·jvm·c++
susplus10 小时前
【HTTP协议】HTTP介绍及基础【C语言爬虫实现】
c语言·爬虫·http·万维网
2601_9609067210 小时前
Gemini与微软Copilot的中断报告数量同步上升
随机森林·逻辑回归·深度优先·广度优先
xx~t11 小时前
嵌入式——Linux软件编程——数据库
linux·c语言·数据库·oracle·sqlite
水饺编程11 小时前
第5章,[Win32 章节] :圆角矩形教学插图绘制程序
c语言·c++·windows·visual studio
是隼人11 小时前
buuctf-pwn PWN4(双引号的转义缺陷)题解(学习过程持续更新)
c语言·学习·安全·pwn入门·ctf入门