前言

查阅了网上许多关于通过DFS算法对有向图中所有简单回路的查找,发现有很多关于使用DFS求解有向回路中所有简单回路的帖子,(在按照节点编号情况下)但大多数仅仅寻找了编号递增的回路。又或者未对结果去重。P.S.下述有向图中所有节点均使用数字进行编号,如节点0、节点1 \(\cdots\)

1. 算法描述

本算法基于DFS,思路与传统DFS基本类似,只不过在遍历过程中对所经过的路径通过一个栈进行保存,当找到回路时,检测此条回路是否已经在结果集中出现,若未出现,则将其放入结果集。本过程中比较关键的两步是DFS结果集去重



关于DFS。每当从一个新的顶点出发对其进行递归的深度遍历时,我们维护一个新的节点已访问数组visited[]以及一个一维的回路栈loopStack。当访问到节点v时,若v已经被访问,即visited[v]==true时,扫描栈中是节点v是否已经出现过,当节点v已经在栈中出现过,则说明此时出现一条回路,我们将其加入结果集。若节点v未被访问,此时置visited[v]=true并将节点v入栈,并对v的所有邻接点进行访问,重复以上操作。到最深处(即已无邻接点未遍历)进行回溯处理,即将栈顶元素退栈。



关于去重。当需要在结果集中加入新找到的一条回路时,需要对结果集扫描,判断此条回路是否已经出现过。但在一个有向图中很容易发现回路\(0\rightarrow1\rightarrow2\rightarrow3(\rightarrow0)\)与回路\(2\rightarrow3\rightarrow1\rightarrow0(\rightarrow2)\)是两条相同的回路,那么在结果集中我们需要对此类回路进行去重,此处我的具体做法是使用两个指针i、j对将要比较的两条回路同时进行扫描比较,指针i指向第一条回路的起始位置,指针j指向第二条回路中,与指针i指向位置元素相等的位置,记录两个回路中相等的元素个数count,当count==loop.size()时,我们称这两条回路为同一条回路,否则将新回路加入结果集。

算法具体步骤:

  1. 从v出发对图进行深度遍历若此节点已访问则转2,否则转3。
  2. 若此节点已经在loopStack中出现,表明有回路存在,判断回路是否已经在结果集loopStacks中出现,若没出现过,则放入结果集。
  3. 置Visited[v]=true,节点v入栈,对v的邻接顶点继续进行深搜。当搜索完所有邻接顶点,栈顶元素退栈。

###2. 算法实现
完整代码在github,请[点击这里](https://github.com/geTiger/FindLoopsInGraph/blob/master/FindAllSimpleLoopsDFS.h)
DFS部分实现
```
void DFS(int v) {
int position = FindInVector(loopStack, v);
if (position ==-1 && visited[v])
visited[v] =false;
if (visited[v] == 1) {
if (position >=0) {
vector loop;
//将环单独拿出并放入结果集
for (int i = position; i for (int j = 0; j < adjMatrixSize; j++) {
if (adjMatrix[v][j] == 1)
DFS(j);
}

loopStack.pop_back();

}

<br>

结果集去重部分

void AddLoopStack(vector loop) {

bool haveThisLoop = false;

int count,begin;

if (loopStacks.size() == 0)

loopStacks.push_back(loop);

else {

for (int i = 0; i < loopStacks.size(); i++) {//遍历结果集中的每一个回路

count = 0;

begin = 0;

if (loop.size() == loopStacks[i].size()) {//若长度相等则进一步比较

//为方便比较,找到结果集中第i条回路中与loop进行匹配的起点

for (int k = 0; k < loopStacks[i].size(); k++) {

if (loop[0] == loopStacks[i][k])

begin = k;

}

//j指针从待添加结果集的loop数组的头部开始扫描

//k指针从上述所找出的与loop数组比较的起点开始扫描

for (int j =0,k=begin; j < loop.size(); j++, k = (k + 1) % (loopStacks[i].size())) {

if (loop[j] == loopStacks[i][k])

count++;

}

if (count == loop.size()) {

//haveThisLoop = true;

//break;

return;

}

}

}//end else

	loopStacks.push_back(loop);
}

}


###3. 算法复杂度分析
假设存在n个顶点,考虑最坏情况下,有向图为有向完全图,那么可能的回路个数就是$C_n^2+C_n^3+\cdots+C_n^n=2^n-n-1$个回路,另外需要对n个顶点均需要DFS,而每条边都需要经过,加上去重的部分,所以时间复杂度为$O(n^32^n)$,需要保存所有回路的空间,故空间复杂度为$O(2^n)$。
<br>
<hr>
参考资料
严蔚敏数据结构(C语言版)

通过DFS求解有向图(邻接表存储)中所有简单回路的更多相关文章

  1. 数据结构(11) -- 邻接表存储图的DFS和BFS

    /////////////////////////////////////////////////////////////// //图的邻接表表示法以及DFS和BFS //////////////// ...

  2. 邻接表存储图,DFS遍历图的java代码实现

    import java.util.*; public class Main{ static int MAX_VERTEXNUM = 100; static int [] visited = new i ...

  3. 数据结构之---C语言实现图的邻接表存储表示

    // 图的数组(邻接矩阵)存储表示 #include <stdio.h> #include <stdlib.h> #include <string.h> #defi ...

  4. 图的邻接表存储 c实现

    图的邻接表存储 c实现 (转载) 用到的数据结构是 一个是顶点表,包括顶点和指向下一个邻接点的指针 一个是边表, 数据结构跟顶点不同,存储的是顶点的序号,和指向下一个的指针 刚开始的时候把顶点表初始化 ...

  5. PTA 邻接表存储图的广度优先遍历(20 分)

    6-2 邻接表存储图的广度优先遍历(20 分) 试实现邻接表存储图的广度优先遍历. 函数接口定义: void BFS ( LGraph Graph, Vertex S, void (*Visit)(V ...

  6. hdu 4707 Pet(DFS &amp;&amp; 邻接表)

    Pet Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submis ...

  7. PTA 邻接表存储图的广度优先遍历

    试实现邻接表存储图的广度优先遍历. 函数接口定义: void BFS ( LGraph Graph, Vertex S, void (*Visit)(Vertex) ) 其中LGraph是邻接表存储的 ...

  8. DS实验题 Old_Driver UnionFindSet结构 指针实现邻接表存储

    题目见前文:DS实验题 Old_Driver UnionFindSet结构 这里使用邻接表存储敌人之间的关系,邻接表用指针实现: // // main.cpp // Old_Driver3 // // ...

  9. 图的邻接表存储表示(C)

    //---------图的邻接表存储表示------- #include<stdio.h> #include<stdlib.h> #define MAX_VERTEXT_NUM ...

随机推荐

  1. 【sql注入】浅谈sql注入中的Post注入

    [sql注入]浅谈sql注入中的Post注入 本文来源:i春秋学院 00x01在许多交流群中,我看见很多朋友对于post注入很是迷茫,曾几何,我也是这样,因为我们都被复杂化了,想的太辅助了所以导致现在 ...

  2. 基于kNN的手写字体识别——《机器学习实战》笔记

    看完一节<机器学习实战>,算是踏入ML的大门了吧!这里就详细讲一下一个demo:使用kNN算法实现手写字体的简单识别 kNN 先简单介绍一下kNN,就是所谓的K-近邻算法: [作用原理]: ...

  3. RestTemplate远程调用POST请求:HTTP 415 Unsupported Media Type

    这是本项目的接口 称为client @POST @Path("/{urlcode}") @Consumes(MediaTypes.JSON_UTF_8) @Produces(Med ...

  4. 第63节:Java中的Spring MVC简介笔记

    前言 感谢! 承蒙关照~ Java中的Spring MVC简介笔记 MVC简介 Spring MVC 基本概念 Spring MVC 项目搭建 maven 使用Spring MVC进行开发 实现数据绑 ...

  5. Java面试集合(五)

    1. 继承 在Java中的三大特性中存在一种为继承,继承究竟是用来解决什么问题的呢?在我们写代码的时候,我们会在一些类中使用相同的属性和方法,如两个不同的人(类),共同都有年龄,身高,体重等. 那么我 ...

  6. python之集合(set)学习

    集合(set) 集合是一个无序的不重复元素序列,使用大括号({}).set()函数创建集合, 注意:创建一个空集合必须用set()而不是{},因为{}是用来创建一个空字典. 集合是无序的.不重复的.没 ...

  7. multiprocessing- 基于进程的并行性

    介绍 multiprocessing是一个使用类似于threading模块的API支持生成进程的包.该multiprocessing软件包提供本地和远程并发,通过使用子进程而不是线程有效地支持 全局解 ...

  8. vue 移动端屏幕适配 使用rem

    要想移动端适配 并使用 rem  您需要先看这篇文章,配置好less ➡️ 在vue 中使用 less,就可以使用rem了 如果项目已经开发的差不多了,没有用到rem 又要使用rem,您用这招. po ...

  9. (转)python生态环境简介

    Python生态环境简介 作者: Mir Nazim 原文: Python Ecosystem - An Introduction 译者: dccrazyboy  原译: Python生态环境简介 当 ...

  10. 记一次eureka客户端注册失败的问题

    最近老大给了一台新服务器,做测试用,从装软件开始,到把整个环境搭起来,因为之前没有完成搭过,所以费了几天时间,最后总算是成功跑起来了.昨天早上来更新下项目,在测试服务器上重新发布了一下,结果发现服务注 ...