一,问题描述

给定一个有向图G=(V,E),将之进行拓扑排序,如果图有环,则提示异常。

要想实现图的算法,如拓扑排序、最短路径……并运行看输出结果,首先就得构造一个图。由于构造图的方式有很多种,这里假设图的数据存储在一个文件中,

每一行包含如下的信息:
LinkID,SourceID,DestinationID,Cost
其中,LinkID为该有向边的索引,SourceID为该有向边的起始顶点的索引,DestinationID为该有向边的终止顶点的索引,Cost为该有向边的权重。

0,0,1,1
1,0,2,2
2,0,3,1
3,2,1,3
4,3,1,1
5,2,3,1
6,3,2,1

(以上示例引用自网上,该图仅用来表示存储图信息的文件内容的格式,对拓扑排序而言,上图显然存在环)

对于以下的拓扑排序程序,只用到了SourceID,和DestionatinID这两个字段。拓扑序列以顶点的索引表示。后续会实现无向图的最短路径算法,就会用到Cost这个字段啦!!!

二,算法实现思路

拓扑排序,其实就是寻找一个入度为0的顶点,该顶点是拓扑排序中的第一个顶点序列,将之标记删除,然后将与该顶点相邻接的顶点的入度减1,再继续寻找入度为0的顶点,直至所有的顶点都已经标记删除或者图中有环。

从上可以看出,关键是寻找入度为0的顶点。

一种方式是遍历整个图中的顶点,找出入度为0的顶点,然后标记删除该顶点,更新相关顶点的入度,由于图中有V个顶点,每次找出入度为0的顶点后会更新相关顶点的入度,因此下一次又要重新扫描图中所有的顶点。故时间复杂度为O(V^2)

由于删除入度为0的顶点时,只会更新与它邻接的顶点的入度,即只会影响与之邻接的顶点。但是上面的方式却遍历了图中所有的顶点的入度。

改进的另一种方式是:先将入度为0的顶点放在栈或者队列中。当队列不空时,删除一个顶点v,然后更新与顶点v邻接的顶点的入度。只要有一个顶点的入度降为0,则将之入队列。此时,拓扑排序就是顶点出队的顺序。该算法的时间复杂度为O(V+E)

三,拓扑排序方法的实现

该算法借助队列来实现时,感觉与 二叉树的 层序遍历算法很相似啊。说明这里面有广度优先的思想。

第一步:遍历图中所有的顶点,将入度为0的顶点 入队列。

第二步:从队列中出一个顶点,打印顶点,更新该顶点的邻接点的入度(减1),如果邻接点的入度减1之后变成了0,则将该邻接点入队列。

第三步:一直执行上面 第二步,直到队列为空。

  1. public void topoSort() throws Exception{
  2. int count = 0;//判断是否所有的顶点都出队了,若有顶点未入队(组成环的顶点),则这些顶点肯定不会出队
  3.  
  4. Queue<Vertex> queue = new LinkedList<>();// 拓扑排序中用到的栈,也可用队列.
  5. //扫描所有的顶点,将入度为0的顶点入队列
  6. Collection<Vertex> vertexs = directedGraph.values();
  7. for (Vertex vertex : vertexs)
  8. if(vertex.inDegree == 0)
  9. queue.offer(vertex);
  10. //度为0的顶点出队列并且更新它的邻接点的入度
  11. while(!queue.isEmpty()){
  12. Vertex v = queue.poll();
  13. System.out.print(v.vertexLabel + " ");//输出拓扑排序的顺序
  14. count++;
  15. for (Edge e : v.adjEdges)
  16. if(--e.endVertex.inDegree == 0)
  17. queue.offer(e.endVertex);
  18. }
  19. if(count != directedGraph.size())
  20. throw new Exception("Graph has circle");
  21. }

第7行for循环:先将图中所有入度为0的顶点入队列。

第11行while循环:将入度为0的顶点出队列,并更新与之邻接的顶点的入度,若邻接顶点的入度降为0,则入队列(第16行if语句)。

第19行if语句判断图中是否有环。因为,只有在每个顶点出队时,count++。对于组成环的顶点,是不可能入队列的,因为组成环的顶点的入度不可能为0(第16行if语句不会成立).

因此,如果有环,count的值 一定小于图中顶点的个数。

四,完整代码实现

DirectedGraph.java中定义了图 数据结构,(图的实现可参考:数据结构--图 的JAVA实现(上))。并根据FileUtil.java中得到的字符串构造图。

构造 图之后,topoSort方法实现了拓扑排序。

  1. import java.util.Collection;
  2. import java.util.LinkedHashMap;
  3. import java.util.LinkedList;
  4. import java.util.List;
  5. import java.util.Map;
  6. import java.util.Queue;
  7.  
  8. /*
  9. * 用来实现拓扑排序的有向无环图
  10. */
  11. public class DirectedGraph {
  12.  
  13. private class Vertex{
  14. private String vertexLabel;// 顶点标识
  15. private List<Edge> adjEdges;
  16. private int inDegree;// 该顶点的入度
  17.  
  18. public Vertex(String verTtexLabel) {
  19. this.vertexLabel = verTtexLabel;
  20. inDegree = 0;
  21. adjEdges = new LinkedList<Edge>();
  22. }
  23. }
  24.  
  25. private class Edge {
  26. private Vertex endVertex;
  27.  
  28. // private double weight;
  29. public Edge(Vertex endVertex) {
  30. this.endVertex = endVertex;
  31. }
  32. }
  33.  
  34. private Map<String, Vertex> directedGraph;
  35.  
  36. public DirectedGraph(String graphContent) {
  37. directedGraph = new LinkedHashMap<String, DirectedGraph.Vertex>();
  38. buildGraph(graphContent);
  39. }
  40.  
  41. private void buildGraph(String graphContent) {
  42. String[] lines = graphContent.split("\n");
  43. Vertex startNode, endNode;
  44. String startNodeLabel, endNodeLabel;
  45. Edge e;
  46. for (int i = 0; i < lines.length; i++) {
  47. String[] nodesInfo = lines[i].split(",");
  48. startNodeLabel = nodesInfo[1];
  49. endNodeLabel = nodesInfo[2];
  50. startNode = directedGraph.get(startNodeLabel);
  51. if(startNode == null){
  52. startNode = new Vertex(startNodeLabel);
  53. directedGraph.put(startNodeLabel, startNode);
  54. }
  55. endNode = directedGraph.get(endNodeLabel);
  56. if(endNode == null){
  57. endNode = new Vertex(endNodeLabel);
  58. directedGraph.put(endNodeLabel, endNode);
  59. }
  60.  
  61. e = new Edge(endNode);//每读入一行代表一条边
  62. startNode.adjEdges.add(e);//每读入一行数据,起始顶点添加一条边
  63. endNode.inDegree++;//每读入一行数据,终止顶点入度加1
  64. }
  65. }
  66.  
  67. public void topoSort() throws Exception{
  68. int count = 0;
  69.  
  70. Queue<Vertex> queue = new LinkedList<>();// 拓扑排序中用到的栈,也可用队列.
  71. //扫描所有的顶点,将入度为0的顶点入队列
  72. Collection<Vertex> vertexs = directedGraph.values();
  73. for (Vertex vertex : vertexs)
  74. if(vertex.inDegree == 0)
  75. queue.offer(vertex);
  76.  
  77. while(!queue.isEmpty()){
  78. Vertex v = queue.poll();
  79. System.out.print(v.vertexLabel + " ");
  80. count++;
  81. for (Edge e : v.adjEdges)
  82. if(--e.endVertex.inDegree == 0)
  83. queue.offer(e.endVertex);
  84. }
  85. if(count != directedGraph.size())
  86. throw new Exception("Graph has circle");
  87. }
  88. }

FileUtil.java负责从文件中读取图的信息。将文件内容转换成 第一点 中描述的字符串格式。--该类来源于网络

  1. import java.io.BufferedReader;
  2. import java.io.BufferedWriter;
  3. import java.io.Closeable;
  4. import java.io.File;
  5. import java.io.FileReader;
  6. import java.io.FileWriter;
  7. import java.io.IOException;
  8.  
  9. public final class FileUtil
  10. {
  11. /**
  12. * 读取文件并按行输出
  13. * @param filePath
  14. * @param spec 允许解析的最大行数, spec==null时,解析所有行
  15. * @return
  16. * @author
  17. * @since 2016-3-1
  18. */
  19. public static String read(final String filePath, final Integer spec)
  20. {
  21. File file = new File(filePath);
  22. // 当文件不存在或者不可读时
  23. if ((!isFileExists(file)) || (!file.canRead()))
  24. {
  25. System.out.println("file [" + filePath + "] is not exist or cannot read!!!");
  26. return null;
  27. }
  28.  
  29. BufferedReader br = null;
  30. FileReader fb = null;
  31. StringBuffer sb = new StringBuffer();
  32. try
  33. {
  34. fb = new FileReader(file);
  35. br = new BufferedReader(fb);
  36.  
  37. String str = null;
  38. int index = 0;
  39. while (((spec == null) || index++ < spec) && (str = br.readLine()) != null)
  40. {
  41. sb.append(str + "\n");
  42. // System.out.println(str);
  43.  
  44. }
  45. }
  46. catch (IOException e)
  47. {
  48. e.printStackTrace();
  49. }
  50. finally
  51. {
  52. closeQuietly(br);
  53. closeQuietly(fb);
  54. }
  55.  
  56. return sb.toString();
  57. }
  58. /**
  59. * 写文件
  60. * @param filePath 输出文件路径
  61. * @param content 要写入的内容
  62. * @param append 是否追加
  63. * @return
  64. * @author s00274007
  65. * @since 2016-3-1
  66. */
  67. public static int write(final String filePath, final String content, final boolean append)
  68. {
  69. File file = new File(filePath);
  70. if (content == null)
  71. {
  72. System.out.println("file [" + filePath + "] invalid!!!");
  73. return 0;
  74. }
  75.  
  76. // 当文件存在但不可写时
  77. if (isFileExists(file) && (!file.canRead()))
  78. {
  79. return 0;
  80. }
  81.  
  82. FileWriter fw = null;
  83. BufferedWriter bw = null;
  84. try
  85. {
  86. if (!isFileExists(file))
  87. {
  88. file.createNewFile();
  89. }
  90.  
  91. fw = new FileWriter(file, append);
  92. bw = new BufferedWriter(fw);
  93.  
  94. bw.write(content);
  95. }
  96. catch (IOException e)
  97. {
  98. e.printStackTrace();
  99. return 0;
  100. }
  101. finally
  102. {
  103. closeQuietly(bw);
  104. closeQuietly(fw);
  105. }
  106.  
  107. return 1;
  108. }
  109.  
  110. private static void closeQuietly(Closeable closeable)
  111. {
  112. try
  113. {
  114. if (closeable != null)
  115. {
  116. closeable.close();
  117. }
  118. }
  119. catch (IOException e)
  120. {
  121. }
  122. }
  123.  
  124. private static boolean isFileExists(final File file)
  125. {
  126. if (file.exists() && file.isFile())
  127. {
  128. return true;
  129. }
  130.  
  131. return false;
  132. }
  133. }

测试类:TestTopoSort.java

  1. public class TestTopoSort {
  2. public static void main(String[] args) {
  3. String graphFilePath;
  4. if(args.length == 0)
  5. graphFilePath = "F:\\xxx";
  6. else
  7. graphFilePath = args[0];
  8.  
  9. String graphContent = FileUtil.read(graphFilePath, null);//从文件中读取图的数据
  10. DirectedGraph directedGraph = new DirectedGraph(graphContent);
  11. try{
  12. directedGraph.topoSort();
  13. }catch(Exception e){
  14. System.out.println("graph has circle");
  15. e.printStackTrace();
  16. }
  17. }
  18. }

有向图的拓扑排序算法JAVA实现的更多相关文章

  1. 有向图的拓扑排序的理解和简单实现(Java)

    如果图中存在环(回路),那么该图不存在拓扑排序,在这里我们讨论的都是无环的有向图. 什么是拓扑排序 一个例子 对于一部电影的制作过程,我们可以看成是一个项目工程.所有的工程都可以分为若干个" ...

  2. 算法笔记_023:拓扑排序(Java)

    目录 1 问题描述 2 解决方案 2.1 基于减治法实现 2.2 基于深度优先查找实现 1 问题描述 给定一个有向图,求取此图的拓扑排序序列. 那么,何为拓扑排序? 定义:将有向图中的顶点以线性方式进 ...

  3. 有向图和拓扑排序Java实现

    package practice; import java.util.ArrayDeque; import java.util.Iterator; import java.util.Stack; pu ...

  4. 无前趋的顶点优先的拓扑排序方法(JAVA)(转载http://128kj.iteye.com/blog/1706968)

    无前趋的顶点优先的拓扑排序方法 该方法的每一步总是输出当前无前趋(即人度为零)的顶点,其抽象算法可描述为:     NonPreFirstTopSort(G){//优先输出无前趋的顶点       w ...

  5. 八大排序算法Java

    目录(?)[-] 概述 插入排序直接插入排序Straight Insertion Sort 插入排序希尔排序Shells Sort 选择排序简单选择排序Simple Selection Sort 选择 ...

  6. C++编程练习(12)----“有向图的拓扑排序“

    设G={V,E}是一个具有 n 个顶点的有向图,V中的顶点序列 v1,v2,......,vn,满足若从顶点 vi 到 vj 有一条路径,则在顶点序列中顶点 vi 必在顶点 vj 之前.则称这样的顶点 ...

  7. 八大排序算法Java实现

    本文对常见的排序算法进行了总结. 常见排序算法如下: 直接插入排序 希尔排序 简单选择排序 堆排序 冒泡排序 快速排序 归并排序 基数排序 它们都属于内部排序,也就是只考虑数据量较小仅需要使用内存的排 ...

  8. 排序算法(Java实现)

    这几天一直在看严蔚敏老师的那本<数据结构>那本书.之前第一次学懵懵逼逼,当再次看的时候,发觉写的是非常详细,非常的好. 那就把相关的排序算法用我熟悉的Java语言记录下来了.以下排序算法是 ...

  9. 6种基础排序算法java源码+图文解析[面试宝典]

    一.概述 作为一个合格的程序员,算法是必备技能,特此总结6大基础算法.java版强烈推荐<算法第四版>非常适合入手,所有算法网上可以找到源码下载. PS:本文讲解算法分三步:1.思想2.图 ...

随机推荐

  1. ASP.NET WebAPI 15 CORS

    同源策略 首先基于安全的原因,浏览器是存在同源策略这个机制的,同源策略阻止从一个源加载的文档或脚本获取或设置另一个源加载的文档的属性. 对于同源必须要求URL在如下几个方面相同: 网络协议(http与 ...

  2. 【背景建模】PBAS

    Pixel-Based Adaptive Segmenter(PBAS)检测算法,是基于像素的无参数模型,该算法结合了SACON和VIBE两个算法的优势,并在这两个算法的基础上改进而来,SACON和V ...

  3. Android从零开始——Android开发环境的安装

    Android开发环境的安装 1 IDE Android可以使用开发的IDE有Eclipse 或者 Android Studio.Android Studio还处于v 0.1.x版本,是early a ...

  4. Java Map按Value排序

    Map是键值对的集合接口,它的实现类主要包括:HashMap,TreeMap,Hashtable以及LinkedHashMap等. TreeMap:基于红黑树(Red-Black tree)的 Nav ...

  5. emberjs初学记要

    code { margin: 0; padding: 0; white-space: pre; border: none; background: transparent; } code, pre t ...

  6. js和html5实现画板

    html5新添了一个重要又强大的标签元素<canvas>,该标签真有彻底替换掉flash的尽头,现在很多网页游戏就是用<canvas>完成的,下面代码就是用该标签制作的一个画板 ...

  7. C标准库<signal.h>实现

    本文地址:http://www.cnblogs.com/archimedes/p/c-library-signal.html,转载请注明源地址. 背景知识 signal.h是C标准函数库中的信号处理部 ...

  8. 认识Runtime2

    我定义了一个Person类作为测试. 其中Person.h: // // Person.h // Test // // Created by zhanggui on 15/8/16. // Copyr ...

  9. iOS 抽象工厂模式

    iOS 抽象工厂模式 什么是抽象工厂模式 简单了解一下 按照惯例,我们先了解一下什么是抽象工厂模式.抽象工厂模式和工厂方法模式很相似,但是抽象工厂模式将抽象发挥的更加极致,是三种工厂模式中最抽象的一种 ...

  10. iOS之 APP异常捕获反馈给服务器

    在我们开发的app中, 不可避免的, 有时候用户使用软件会崩溃.  我们就需要捕获异常, 可以在入口类中加入相应的代码, 可以在每次用户打开程序的时候, 检查一下沙盒中是否有崩溃日志, 如果有, 可以 ...