转自:http://www.cnblogs.com/dugujiujian/archive/2011/08/19/2146042.html
最近项目中需要研究了一下有向图的环路问题。一个IT企业中有成千上万个应用,各个应用之间都是相互依赖的,一个用户请求进来后,会调用一系列应用,比如A调B、B调C、C调D等。这样所有的应用形成一个有向图,那么如果这个有向图中出现了环路,就悲剧了,用户的请求如果进入这个环路,那么他永远也得不到响应。所以就有需要去判断这个应用组成的有向图中是否含有环路,如果有就要打印出所有的环路,想办法将这些环路拆解。
说简单了,就是算法中的一个简单问题,在有向图中找到所有的环路。请教了宿舍的算法高手just,加上我自己的理解,产生了一些思路:
1. DFS树,找所有后退边
首先将有向图转化为一颗DFS树,如果碰到后退边,那么肯定存在环,打印之。那么实现的时候利用深度搜索维护一个节点是否被访问的数组visited[],如果搜索到已经被访问过的节点,那么就是一条环。这个可以过滤掉交叉边的情况,因为交叉边的节点还未被访问。搜索的路径用栈来维护,这样方便打印。为了方便,用java实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
import
java.util.ArrayList;
public
class
test {
static
private
final
int
POINT_NUM =
9
;
static
private
int
[] visited={
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
};
static
private
int
[][] e={
{
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
1
},
{
0
,
0
,
0
,
1
,
1
,
0
,
0
,
0
,
0
},
{
1
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
},
{
0
,
0
,
0
,
0
,
0
,
0
,
0
,
1
,
0
},
{
0
,
0
,
0
,
0
,
0
,
0
,
0
,
1
,
0
},
{
0
,
1
,
0
,
0
,
0
,
0
,
0
,
0
,
0
},
{
1
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
},
{
0
,
0
,
1
,
0
,
0
,
0
,
1
,
0
,
0
},
{
0
,
0
,
0
,
0
,
0
,
1
,
0
,
0
,
0
}};
static
ArrayList<Integer> trace=
new
ArrayList<Integer>();
static
boolean
hasCycle=
false
;
public
static
void
main(String[] args) {
findCycle(
0
);
if
(!hasCycle)
System.out.println(
"No Cycle."
);
}
static
void
findCycle(
int
v)
{
if
(visited[v] ==
1
){
if
((j=trace.indexOf(v))!=-
1
)
{
hasCycle=
true
;
System.out.print(
"Cycle:"
);
while
(j<trace.size())
{
System.out.print(trace.get(j)+
" "
);
j++;
}
System.out.print(
"\n"
);
return
;
}
return
;
}
visited[v]=
1
;
trace.add(v);
for
(
int
i=
0
;i<POINT_NUM;i++)
{
if
(e[v][i]==
1
)
findCycle(i);
}
trace.remove(trace.size()-
1
);
}
}
|
2. 真的对吗?
大家仔细运行上面的程序会发现,有一部分环路是打印不出来的,为什么呢?问题出在这个visited数组上面,上面程序的逻辑是如果搜索到访问过的节点,则表示找到了一条环。考虑这样的情况,有5个点,连通情况是1->2, 2->3, 3->4, 4->5, 5->1, 3->5。这时程序先找到1、2、3、4、5这条环,那么栈中的元素从底向上是1、2、3、4、5,这时5出栈,4出栈,3指向5,程序执行findCycle(5)时发现,5已经访问过,但是不在栈中,直接return了,所以1、2、3、5这条环没有找出来。问题的关键就在于5已经访问过了,程序认为已经找到了一条环,实际上这还不是一条环,等走到5指向1时,才发现有了一条新的环。因为这时5已经不在环中,所以这条环没有被发现。那找到问题的关键后,我们进行改进:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
import
java.util.ArrayList;
public
class
test {
static
private
final
int
POINT_NUM =
9
;
static
private
int
[] visited={
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
};
static
private
int
[][] e={
{
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
1
},
{
0
,
0
,
0
,
1
,
1
,
0
,
0
,
0
,
0
},
{
1
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
},
{
0
,
0
,
0
,
0
,
0
,
0
,
0
,
1
,
0
},
{
0
,
0
,
0
,
0
,
0
,
0
,
0
,
1
,
0
},
{
0
,
1
,
0
,
0
,
0
,
0
,
0
,
0
,
0
},
{
1
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
},
{
0
,
0
,
1
,
0
,
0
,
0
,
1
,
0
,
0
},
{
0
,
0
,
0
,
0
,
0
,
1
,
0
,
0
,
0
}};
static
ArrayList<Integer> trace=
new
ArrayList<Integer>();
static
boolean
hasCycle=
false
;
public
static
void
main(String[] args) {
findCycle(
0
);
if
(!hasCycle)
System.out.println(
"No Cycle."
);
}
static
void
findCycle(
int
v)
{
if
(!trace.contains(v) && visited[v] ==
1
){
visited[v] =
0
;
}
if
(visited[v] ==
1
){
if
((j=trace.indexOf(v))!=-
1
)
{
hasCycle=
true
;
System.out.print(
"Cycle:"
);
while
(j<trace.size())
{
System.out.print(trace.get(j)+
" "
);
j++;
}
System.out.print(
"\n"
);
return
;
}
return
;
}
trace.add(v);
for
(
int
i=
0
;i<POINT_NUM;i++)
{
if
(e[v][i]==
1
)
findCycle(i);
}
trace.remove(trace.size()-
1
);
}
}
|
3. 进一步探索
通过上面的改进,程序可以找出所有的环了。我们再来细细琢磨一下这个visited数组,想想它是用来干什么的,它的作用到底是什么?visited数组的作用是,当找到一个已经被访问过的元素时,我们认为找到了一条后退边,即找到了一个环。它本质上的作用是,我们找到的已经访问过的元素是栈中的一个元素,这样肯定找到了一条环。那我们其实不需要visited这个数组了。所以就有了以下的改进:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
import
java.util.ArrayList;
public
class
test {
static
private
final
int
POINT_NUM =
9
;
static
private
int
[][] e={
{
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
1
},
{
0
,
0
,
0
,
1
,
1
,
0
,
0
,
0
,
0
},
{
1
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
},
{
0
,
0
,
0
,
0
,
0
,
0
,
0
,
1
,
0
},
{
0
,
0
,
0
,
0
,
0
,
0
,
0
,
1
,
0
},
{
0
,
1
,
0
,
0
,
0
,
0
,
0
,
0
,
0
},
{
1
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
},
{
0
,
0
,
1
,
0
,
0
,
0
,
1
,
0
,
0
},
{
0
,
0
,
0
,
0
,
0
,
1
,
0
,
0
,
0
}};
static
ArrayList<Integer> trace=
new
ArrayList<Integer>();
static
boolean
hasCycle=
false
;
public
static
void
main(String[] args) {
findCycle(
0
);
if
(!hasCycle)
System.out.println(
"No Cycle."
);
}
static
void
findCycle(
int
v)
{
if
((j=trace.indexOf(v))!=-
1
)
{
hasCycle=
true
;
System.out.print(
"Cycle:"
);
while
(j<trace.size())
{
System.out.print(trace.get(j)+
" "
);
j++;
}
System.out.print(
"\n"
);
return
;
}
trace.add(v);
for
(
int
i=
0
;i<POINT_NUM;i++)
{
if
(e[v][i]==
1
)
findCycle(i);
}
trace.remove(trace.size()-
1
);
}
}
|