有向图中打印所有的环路

2024-09-01 04:32
文章标签 有向图 所有 打印 环路

本文主要是介绍有向图中打印所有的环路,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转自:http://www.cnblogs.com/dugujiujian/archive/2011/08/19/2146042.html 

最近项目中需要研究了一下有向图的环路问题。一个IT企业中有成千上万个应用,各个应用之间都是相互依赖的,一个用户请求进来后,会调用一系列应用,比如ABBCCD等。这样所有的应用形成一个有向图,那么如果这个有向图中出现了环路,就悲剧了,用户的请求如果进入这个环路,那么他永远也得不到响应。所以就有需要去判断这个应用组成的有向图中是否含有环路,如果有就要打印出所有的环路,想办法将这些环路拆解。

      说简单了,就是算法中的一个简单问题,在有向图中找到所有的环路。请教了宿舍的算法高手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。这时程序先找到12345这条环,那么栈中的元素从底向上是12345,这时5出栈,4出栈,3指向5,程序执行findCycle(5)时发现,5已经访问过,但是不在栈中,直接return了,所以1235这条环没有找出来。问题的关键就在于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 );
     }
}

这篇关于有向图中打印所有的环路的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1125946

相关文章

Python实现将MySQL中所有表的数据都导出为CSV文件并压缩

《Python实现将MySQL中所有表的数据都导出为CSV文件并压缩》这篇文章主要为大家详细介绍了如何使用Python将MySQL数据库中所有表的数据都导出为CSV文件到一个目录,并压缩为zip文件到... python将mysql数据库中所有表的数据都导出为CSV文件到一个目录,并压缩为zip文件到另一个

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件

使用Navicat工具比对两个数据库所有表结构的差异案例详解

《使用Navicat工具比对两个数据库所有表结构的差异案例详解》:本文主要介绍如何使用Navicat工具对比两个数据库test_old和test_new,并生成相应的DDLSQL语句,以便将te... 目录概要案例一、如图两个数据库test_old和test_new进行比较:二、开始比较总结概要公司存在多

在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码

《在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码》在MyBatis的XML映射文件中,trim元素用于动态添加SQL语句的一部分,处理前缀、后缀及多余的逗号或连接符,示... 在MyBATis的XML映射文件中,<trim>元素用于动态地添加SQL语句的一部分,例如SET或W

C#实现获得某个枚举的所有名称

《C#实现获得某个枚举的所有名称》这篇文章主要为大家详细介绍了C#如何实现获得某个枚举的所有名称,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下... C#中获得某个枚举的所有名称using System;using System.Collections.Generic;usi

通过C#获取PDF中指定文本或所有文本的字体信息

《通过C#获取PDF中指定文本或所有文本的字体信息》在设计和出版行业中,字体的选择和使用对最终作品的质量有着重要影响,然而,有时我们可能会遇到包含未知字体的PDF文件,这使得我们无法准确地复制或修改文... 目录引言C# 获取PDF中指定文本的字体信息C# 获取PDF文档中用到的所有字体信息引言在设计和出

Collection的所有的方法演示

import java.util.ArrayList;import java.util.Collection;import java.util.Iterator;public class TestCollection {/*** @param args* Collection的所有的方法演示* 此程序没有使用泛型,所以可以添加任意类型* 以后如果写到泛型会补充这一方面的内容*/public s

Temu官方宣导务必将所有的点位材料进行检测-RSL资质检测

关于饰品类产品合规问题宣导: 产品法规RSL要求 RSL测试是根据REACH法规及附录17的要求进行测试。REACH法规是欧洲一项重要的法规,其中包含许多对化学物质进行限制的规定和高度关注物质。 为了确保珠宝首饰的安全性,欧盟REACH法规规定,珠宝首饰上架各大电商平台前必须进行RSLReport(欧盟禁限用化学物质检测报告)资质认证,以确保产品不含对人体有害的化学物质。 RSL-铅,

多数据源的事务处理总是打印很多无用的log日志

之前做了一个项目,需要用到多数据源以及事务处理,在使用事务处理,服务器总是打印很多关于事务处理的log日志(com.atomikos.logging.Slf4jLogger),但是我们根本不会用到这些log日志,反而使得查询一些有用的log日志变得困难。那要如何屏蔽这些log日志呢? 之前的项目是提高项目打印log日志的级别,后来觉得这样治标不治本。 现在有一个更好的方法: 我使用的是log

fastreport打印trichedit分页问题的解决

用fastreport来打印richedit里面的内容。刚开始放一个frxrichview组件到报表上,然后在 var str: TMemoryStream; begin    begin      str:= TMemoryStream.Create;      CurrRichRecord.richedit.Lines.SaveToStream(str);      str.Posit