通过DFS求解有向图(邻接表存储)中所有简单回路
前言
查阅了网上许多关于通过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()时,我们称这两条回路为同一条回路,否则将新回路加入结果集。
算法具体步骤:
- 从v出发对图进行深度遍历若此节点已访问则转2,否则转3。
- 若此节点已经在loopStack中出现,表明有回路存在,判断回路是否已经在结果集loopStacks中出现,若没出现过,则放入结果集。
- 置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求解有向图(邻接表存储)中所有简单回路的更多相关文章
- 数据结构(11) -- 邻接表存储图的DFS和BFS
/////////////////////////////////////////////////////////////// //图的邻接表表示法以及DFS和BFS //////////////// ...
- 邻接表存储图,DFS遍历图的java代码实现
import java.util.*; public class Main{ static int MAX_VERTEXNUM = 100; static int [] visited = new i ...
- 数据结构之---C语言实现图的邻接表存储表示
// 图的数组(邻接矩阵)存储表示 #include <stdio.h> #include <stdlib.h> #include <string.h> #defi ...
- 图的邻接表存储 c实现
图的邻接表存储 c实现 (转载) 用到的数据结构是 一个是顶点表,包括顶点和指向下一个邻接点的指针 一个是边表, 数据结构跟顶点不同,存储的是顶点的序号,和指向下一个的指针 刚开始的时候把顶点表初始化 ...
- PTA 邻接表存储图的广度优先遍历(20 分)
6-2 邻接表存储图的广度优先遍历(20 分) 试实现邻接表存储图的广度优先遍历. 函数接口定义: void BFS ( LGraph Graph, Vertex S, void (*Visit)(V ...
- hdu 4707 Pet(DFS && 邻接表)
Pet Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submis ...
- PTA 邻接表存储图的广度优先遍历
试实现邻接表存储图的广度优先遍历. 函数接口定义: void BFS ( LGraph Graph, Vertex S, void (*Visit)(Vertex) ) 其中LGraph是邻接表存储的 ...
- DS实验题 Old_Driver UnionFindSet结构 指针实现邻接表存储
题目见前文:DS实验题 Old_Driver UnionFindSet结构 这里使用邻接表存储敌人之间的关系,邻接表用指针实现: // // main.cpp // Old_Driver3 // // ...
- 图的邻接表存储表示(C)
//---------图的邻接表存储表示------- #include<stdio.h> #include<stdlib.h> #define MAX_VERTEXT_NUM ...
随机推荐
- C#轻量级通通讯组件StriveEngine —— C/S通信开源demo(2) —— 使用二进制协议 (附源码)
前段时间,有几个研究ESFramework通信框架的朋友对我说,ESFramework有点庞大,对于他们目前的项目来说有点“杀鸡用牛刀”的意思,因为他们的项目不需要文件传送.不需要P2P.不存在好友关 ...
- SQL Server对比两字符串的相似度(函数算法)
一.概述 最近有人问到关于两个字符串求相似度的函数,所以就写了本篇文章,分别是“简单的模糊匹配”,“顺序匹配”,“一对一位置匹配”.在平时的这种函数可能会需要用到,业务需求不一样,这里只给出参照 ...
- 安全运维中基线检查的自动化之ansible工具巧用
i春秋作家:yanzm 原文来自:安全运维中基线检查的自动化之ansible工具巧用 前几周斗哥分享了基线检查获取数据的脚本,但是在面对上百台的服务器,每台服务器上都跑一遍脚本那工作量可想而知,而且都 ...
- Java进程和线程关系及区别
1.定义 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基 ...
- .net core 发布linux报错“The configured user limit (128) on the number of inotify instances has been reached”
https://stackoverflow.com/questions/45875981/error-while-reading-json-file-in-dotnet-core-the-config ...
- tomcat编译超过64k大小的jsp文件报错原因
今天遇到一个问题,首先是在tomcat中间件上跑的web项目,一个jsp文件,因为代码行数实在是太多了,更新了几个版本之后编译报错了,页面打开都是报500的错误,500的报错,知道http协议返回码的 ...
- MyBatis 延迟加载的三种加载方式深入,你get了吗?
延迟加载 延迟加载对主对象都是直接加载,只有对关联对象是延迟加载. 延迟加载可以减轻数据库的压力, 延迟加载不可是一条SQL查询多表信息,这样构不成延迟加载,会形成直接加载. 延迟加载分为三种类型: ...
- 插头dp初探
问题描述 插头dp用于解决一类可基于图连通性递推的问题.用插头来表示轮廓线上的连通性,然后根据连通性与下一位结合讨论进行转移. 表示连通性的方法 与字符串循环最小表示不同,这种方法用于给轮廓线上的联通 ...
- python之N阶乘结果末尾有几个0
算法思路:首先是算阶乘,可以使用内置函数reduce实现,其次是计算结果的末尾有几个0,可以使用除余判断 代码如下: #!/usr/bin/env python#-*-coding:utf-8-*- ...
- 关于sql和MySQL的语句执行顺序(必看!!!)
今天遇到一个问题就是mysql中insert into 和update以及delete语句中能使用as别名吗?目前还在查看,但是在查阅资料时发现了一些有益的知识,给大家分享一下,就是关于sql以及My ...