环检测
完整源码
#include <iostream>
#include <vector>
#include <tuple>
#include <stack>
#include <map>
using namespace std;
int V, E;
//带权有向图
map<int, vector<tuple<int , int , double>>> EWD;
bool marked[]; // v 是否已经被访问过?
bool onStack[]; // v 是否在栈里?
tuple<int , int , double> edgeTo[]; // 到达顶点 v 的最后一条边
stack<tuple<int , int , double>> cycle; // 有向环
void dfs(int v) {
onStack[v] = true;
marked[v] = true;
for(vector<tuple<int, int, double>>::iterator ii = EWD[v].begin(); ii != EWD[v].end(); ii++)
{
int w = get<>(*ii);
if(!cycle.empty()) { // 已经找到了一个有向环
return;
}
else if(!marked[w]) { // 遇见没访问过的顶点继续dfs递归
edgeTo[w] = *ii;
dfs(w);
}
else if(onStack[w]) { // 遇见一个访问过的顶点,并且该顶点在栈里说明发现了一个新环
tuple<int, int, double> f = *ii;
while(get<>(f) != w) {
cycle.push(f);
f = edgeTo[get<>(f)];
}
cycle.push(f);
return ;
}
}
onStack[v] = false;
}
void findCycle() {
for(int v = ; v < V; v++)
if(!marked[v]) dfs(v);
}
void showCycle() {
cout << "Cycle : " << endl;
while(!cycle.empty()) {
tuple<int, int, double> f = cycle.top();
cout << get<>(f) << "->" << get<>(f) << " " << get<>(f) << endl;
cycle.pop();
}
cout << endl;
}
void readData() {
cin >> V >> E;
for(int i = ; i < E ;i++)
{
int v, w;
double weight;
cin >> v >> w >> weight;
EWD[v].push_back(make_tuple(v, w, weight));
}
}
void showData() {
cout << "EdgeWeightedDigraph : " << endl;
for(int v = ; v < V; v++)
{
cout << v << " : ";
for(vector<tuple<int, int, double>>::iterator ii = EWD[v].begin(); ii != EWD[v].end(); ii++)
cout << get<>(*ii) << "->" << get<>(*ii) << " " << get<>(*ii) << " ";
cout << endl;
}
system("pause");
}
int main()
{
readData();
showData();
findCycle();
showCycle();
}
测试运行
模拟数据

运行结果
EdgeWeightedDigraph :
: -> ->
:
: ->
: ->
请按任意键继续. . .
Cycle :
->
->
->
--------------------------------
简单地说
- 从 出发,往左边走,先遇到
,到头了,回不到 ,这不是环;1
- 从 出发,往右边走,先遇到
,继续往深处走,遇到2
,再往深处走,遇到 , 不仅访问过还在栈上面,这是环;3
代码说明
数据类型
tuple<int , int , double> edgeTo[]; // 到达顶点 v 的最后一条边
stack<tuple<int , int , double>> cycle; // 有向环
- 有向边,顶点、顶点、权重,其实本文重点是 环 检测,带不带权重都不重要,算法都一样;
-
来存这条被找出来的有向边;stack
主程序
int main()
{
readData(); // 数据输入
showData(); // 显示有向图
findCycle(); // 找到一个环
showCycle(); // 显示找到的环
}
环检测
void findCycle() {
for(int v = ; v < V; v++)
if(!marked[v]) dfs(v);
}
void dfs(int v) {
onStack[v] = true;
marked[v] = true;
for()
{
if(!cycle.empty()) {
return;
}
else if(!marked[w]) {
edgeTo[w] = *ii;
dfs(w);
}
else if(onStack[w]) {
}
}
onStack[v] = false;
}
- 本质就是
搜索,增加了DFS
数组来表示出现在 本次递归 之中的顶点;onStack
-
数组就是显式地把内存栈的调用过程摆出来;onStack[]
- 两个
等价于else if
,被访问过也要在 本次递归 之中;if(marked[w] && onStack[w])
继续DFS
else if(!marked[w]) { // 遇见没访问过的顶点继续dfs递归
edgeTo[w] = *ii;
dfs(w);
}
-
数组和marked[]
是“打包”使用的,不要多想;DFS
把环存起来
else if(onStack[w]) {
tuple<int, int, double> f = *ii;
while(get<>(f) != w) {
cycle.push(f);
f = edgeTo[get<>(f)];
}
cycle.push(f);
return ;
}
- 因为存的是有向边,顺着边,从
到to
,往回爬,爬到环的起点(就是那个被发现被访问的并且在本次递归中的点)就行了;from
显示环
void showCycle() {
cout << "Cycle : " << endl;
while(!cycle.empty()) {
tuple<int, int, double> f = cycle.top();
cout << get<>(f) << "->" << get<>(f) << " " << get<>(f) << endl;
cycle.pop();
}
cout << endl;
}
- 就是把栈的内容拿出来;
思考体会
- 递归写代码还是很不熟练,这个代码完全是照着
的代码写的,只不过是一种algs4
的实现;C++ STL
-
其实也还不熟练,比如我觉得我应该是可以把C++ STL
用个什么tuple<int, int, double>
还是typedef
打包一下,不用每次都struct
这一串,但是我觉得我要写个环检测,也没必要先穷尽copy
再写吧,嗯,可以以后改进这里;C++ STL
- 其实一开始我在
直接写了个showCycle()
,我一开始写cout << f ;
的输出语句还是用的C++
的C
,后来发现printf
是个好东西,什么都可以装,就直接用cout
了,这里还是要分开写的;cout
- 代码里还是有把功能和具体实现没有分开的问题,比如
,用tuple<int, int, double>
取出来的是get<0>
,以此类推,但是现在感觉这里似乎不要把细节暴露出来比较好。from
参考引用
[1] C++ STL stack
http://www.cplusplus.com/reference/stack/stack/
[2] algs4 Finds a directed cycle in an edge-weighted digraph.
http://algs4.cs.princeton.edu/44sp/EdgeWeightedDirectedCycle.java.html