有向图的拓扑排序算法JAVA实现
一,问题描述
给定一个有向图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,则将该邻接点入队列。
第三步:一直执行上面 第二步,直到队列为空。
public void topoSort() throws Exception{
int count = 0;//判断是否所有的顶点都出队了,若有顶点未入队(组成环的顶点),则这些顶点肯定不会出队
Queue<Vertex> queue = new LinkedList<>();// 拓扑排序中用到的栈,也可用队列.
//扫描所有的顶点,将入度为0的顶点入队列
Collection<Vertex> vertexs = directedGraph.values();
for (Vertex vertex : vertexs)
if(vertex.inDegree == 0)
queue.offer(vertex);
//度为0的顶点出队列并且更新它的邻接点的入度
while(!queue.isEmpty()){
Vertex v = queue.poll();
System.out.print(v.vertexLabel + " ");//输出拓扑排序的顺序
count++;
for (Edge e : v.adjEdges)
if(--e.endVertex.inDegree == 0)
queue.offer(e.endVertex);
}
if(count != directedGraph.size())
throw new Exception("Graph has circle");
}
第7行for循环:先将图中所有入度为0的顶点入队列。
第11行while循环:将入度为0的顶点出队列,并更新与之邻接的顶点的入度,若邻接顶点的入度降为0,则入队列(第16行if语句)。
第19行if语句判断图中是否有环。因为,只有在每个顶点出队时,count++。对于组成环的顶点,是不可能入队列的,因为组成环的顶点的入度不可能为0(第16行if语句不会成立).
因此,如果有环,count的值 一定小于图中顶点的个数。
四,完整代码实现
DirectedGraph.java中定义了图 数据结构,(图的实现可参考:数据结构--图 的JAVA实现(上))。并根据FileUtil.java中得到的字符串构造图。
构造 图之后,topoSort方法实现了拓扑排序。
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue; /*
* 用来实现拓扑排序的有向无环图
*/
public class DirectedGraph { private class Vertex{
private String vertexLabel;// 顶点标识
private List<Edge> adjEdges;
private int inDegree;// 该顶点的入度 public Vertex(String verTtexLabel) {
this.vertexLabel = verTtexLabel;
inDegree = 0;
adjEdges = new LinkedList<Edge>();
}
} private class Edge {
private Vertex endVertex; // private double weight;
public Edge(Vertex endVertex) {
this.endVertex = endVertex;
}
} private Map<String, Vertex> directedGraph; public DirectedGraph(String graphContent) {
directedGraph = new LinkedHashMap<String, DirectedGraph.Vertex>();
buildGraph(graphContent);
} private void buildGraph(String graphContent) {
String[] lines = graphContent.split("\n");
Vertex startNode, endNode;
String startNodeLabel, endNodeLabel;
Edge e;
for (int i = 0; i < lines.length; i++) {
String[] nodesInfo = lines[i].split(",");
startNodeLabel = nodesInfo[1];
endNodeLabel = nodesInfo[2];
startNode = directedGraph.get(startNodeLabel);
if(startNode == null){
startNode = new Vertex(startNodeLabel);
directedGraph.put(startNodeLabel, startNode);
}
endNode = directedGraph.get(endNodeLabel);
if(endNode == null){
endNode = new Vertex(endNodeLabel);
directedGraph.put(endNodeLabel, endNode);
} e = new Edge(endNode);//每读入一行代表一条边
startNode.adjEdges.add(e);//每读入一行数据,起始顶点添加一条边
endNode.inDegree++;//每读入一行数据,终止顶点入度加1
}
} public void topoSort() throws Exception{
int count = 0; Queue<Vertex> queue = new LinkedList<>();// 拓扑排序中用到的栈,也可用队列.
//扫描所有的顶点,将入度为0的顶点入队列
Collection<Vertex> vertexs = directedGraph.values();
for (Vertex vertex : vertexs)
if(vertex.inDegree == 0)
queue.offer(vertex); while(!queue.isEmpty()){
Vertex v = queue.poll();
System.out.print(v.vertexLabel + " ");
count++;
for (Edge e : v.adjEdges)
if(--e.endVertex.inDegree == 0)
queue.offer(e.endVertex);
}
if(count != directedGraph.size())
throw new Exception("Graph has circle");
}
}
FileUtil.java负责从文件中读取图的信息。将文件内容转换成 第一点 中描述的字符串格式。--该类来源于网络
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException; public final class FileUtil
{
/**
* 读取文件并按行输出
* @param filePath
* @param spec 允许解析的最大行数, spec==null时,解析所有行
* @return
* @author
* @since 2016-3-1
*/
public static String read(final String filePath, final Integer spec)
{
File file = new File(filePath);
// 当文件不存在或者不可读时
if ((!isFileExists(file)) || (!file.canRead()))
{
System.out.println("file [" + filePath + "] is not exist or cannot read!!!");
return null;
} BufferedReader br = null;
FileReader fb = null;
StringBuffer sb = new StringBuffer();
try
{
fb = new FileReader(file);
br = new BufferedReader(fb); String str = null;
int index = 0;
while (((spec == null) || index++ < spec) && (str = br.readLine()) != null)
{
sb.append(str + "\n");
// System.out.println(str); }
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
closeQuietly(br);
closeQuietly(fb);
} return sb.toString();
}
/**
* 写文件
* @param filePath 输出文件路径
* @param content 要写入的内容
* @param append 是否追加
* @return
* @author s00274007
* @since 2016-3-1
*/
public static int write(final String filePath, final String content, final boolean append)
{
File file = new File(filePath);
if (content == null)
{
System.out.println("file [" + filePath + "] invalid!!!");
return 0;
} // 当文件存在但不可写时
if (isFileExists(file) && (!file.canRead()))
{
return 0;
} FileWriter fw = null;
BufferedWriter bw = null;
try
{
if (!isFileExists(file))
{
file.createNewFile();
} fw = new FileWriter(file, append);
bw = new BufferedWriter(fw); bw.write(content);
}
catch (IOException e)
{
e.printStackTrace();
return 0;
}
finally
{
closeQuietly(bw);
closeQuietly(fw);
} return 1;
} private static void closeQuietly(Closeable closeable)
{
try
{
if (closeable != null)
{
closeable.close();
}
}
catch (IOException e)
{
}
} private static boolean isFileExists(final File file)
{
if (file.exists() && file.isFile())
{
return true;
} return false;
}
}
测试类:TestTopoSort.java
public class TestTopoSort {
public static void main(String[] args) {
String graphFilePath;
if(args.length == 0)
graphFilePath = "F:\\xxx";
else
graphFilePath = args[0];
String graphContent = FileUtil.read(graphFilePath, null);//从文件中读取图的数据
DirectedGraph directedGraph = new DirectedGraph(graphContent);
try{
directedGraph.topoSort();
}catch(Exception e){
System.out.println("graph has circle");
e.printStackTrace();
}
}
}
有向图的拓扑排序算法JAVA实现的更多相关文章
- 有向图的拓扑排序的理解和简单实现(Java)
如果图中存在环(回路),那么该图不存在拓扑排序,在这里我们讨论的都是无环的有向图. 什么是拓扑排序 一个例子 对于一部电影的制作过程,我们可以看成是一个项目工程.所有的工程都可以分为若干个" ...
- 算法笔记_023:拓扑排序(Java)
目录 1 问题描述 2 解决方案 2.1 基于减治法实现 2.2 基于深度优先查找实现 1 问题描述 给定一个有向图,求取此图的拓扑排序序列. 那么,何为拓扑排序? 定义:将有向图中的顶点以线性方式进 ...
- 有向图和拓扑排序Java实现
package practice; import java.util.ArrayDeque; import java.util.Iterator; import java.util.Stack; pu ...
- 无前趋的顶点优先的拓扑排序方法(JAVA)(转载http://128kj.iteye.com/blog/1706968)
无前趋的顶点优先的拓扑排序方法 该方法的每一步总是输出当前无前趋(即人度为零)的顶点,其抽象算法可描述为: NonPreFirstTopSort(G){//优先输出无前趋的顶点 w ...
- 八大排序算法Java
目录(?)[-] 概述 插入排序直接插入排序Straight Insertion Sort 插入排序希尔排序Shells Sort 选择排序简单选择排序Simple Selection Sort 选择 ...
- C++编程练习(12)----“有向图的拓扑排序“
设G={V,E}是一个具有 n 个顶点的有向图,V中的顶点序列 v1,v2,......,vn,满足若从顶点 vi 到 vj 有一条路径,则在顶点序列中顶点 vi 必在顶点 vj 之前.则称这样的顶点 ...
- 八大排序算法Java实现
本文对常见的排序算法进行了总结. 常见排序算法如下: 直接插入排序 希尔排序 简单选择排序 堆排序 冒泡排序 快速排序 归并排序 基数排序 它们都属于内部排序,也就是只考虑数据量较小仅需要使用内存的排 ...
- 排序算法(Java实现)
这几天一直在看严蔚敏老师的那本<数据结构>那本书.之前第一次学懵懵逼逼,当再次看的时候,发觉写的是非常详细,非常的好. 那就把相关的排序算法用我熟悉的Java语言记录下来了.以下排序算法是 ...
- 6种基础排序算法java源码+图文解析[面试宝典]
一.概述 作为一个合格的程序员,算法是必备技能,特此总结6大基础算法.java版强烈推荐<算法第四版>非常适合入手,所有算法网上可以找到源码下载. PS:本文讲解算法分三步:1.思想2.图 ...
随机推荐
- 从零开始学习Linux(cp 命令)
功能: 复制文件或目录说明: cp指令用于复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则它会把前面指定的所有文件或目录复制到此目录中.若同时指定多个文件或目录, ...
- [moka同学笔记]一、Yii2.0课程笔记(魏曦老师教程)
第一节 第二节 课程内容
- SystemClock.sleep和Thread.sleep的区别(转)
在Java中我们处理线程同步问题时,处理延迟可能会使用Thread类的sleep方法,这里抛开concurrent类的一些方法,其实 Android平台还提供了一个SystemClock.sleep方 ...
- domain规划
user-generated content 用户产生内容 和 admin-generated content 管理员产生内容,要区分开来,便于不同的图片压缩.备份.带宽.CDN.审核方案. 图片(j ...
- 自己写的表格插件autotable
自己写的表格插件autotable 作者:田想兵,个人网址:http://www.lovewebgames.com 这个表格插件所完成的功能是:ajax请求数据,然后动态绑定到指定表格下,格式化,分页 ...
- 关于IE处理margin和padding值超出父元素高度的问题
两个div,父div有padding值,子div有margin-top值,浏览器在解析实际父子位置关系时,他们之间的距离是父padding+子margin-top.现在把父div设置固定高度,并有意让 ...
- 二叉查找树(binary search tree)详解
二叉查找树(Binary Search Tree),也称二叉排序树(binary sorted tree),是指一棵空树或者具有下列性质的二叉树: 若任意节点的左子树不空,则左子树上所有结点的值均小于 ...
- 【读书笔记】iOS-安全地传输用户密码的方法
正确做法:事先生成一对用于加密的公私钥,客户端在登录时,使用公钥将用户的密码加密后,将密文传输到服务器.服务器使用私钥将密码解密,然后加盐之后多次请求MD5,之后再和服务器原来存储的用同样方法处理过的 ...
- iOS开发笔记10:圆点缩放动画、强制更新、远程推送加语音提醒及UIView截屏
1.使用CAReplicatorLayer制作等待动画 CALayer+CABasicAnimation可以制作很多简单的动画效果,之前的博客中介绍的“两个动画”,一个是利用一张渐变色图片+CABas ...
- UVa 109 - SCUD Busters(凸包计算)
题目来源:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=3&pa ...