相关概念
AOV网
- 一项大的工程常被分为多个小的子工程
- 子工程之间可能存在一定的先后顺序,即某些子工程必须在其他的一些子工程完成后才能开始
- 在现代化管理中,人们常用有向图来描述和分析一项工程的计划和实施过程,子工程被称为活动(Activity)
- 以顶点表示活动、有向边表示活动之间的先后关系,这样的图简称为AOV网
- 标准的AOV网必须是一个有向无环图(Directed Acyclic Graph, 简称 DAG)
拓扑排序
- 前驱活动: 有向边起点的活动称为终点的前驱活动
- 只有当一个活动的前驱全部都完成后,这个活动才能进行
- 后继活动: 有向边终点的活动称为起点的后继活动
- 什么是拓扑排序?
- 将AOV网中所有活动排成一个序列,使得每个活动的前驱活动都排在该活动的前面
- 比如上图的拓扑排序结果是: A、B、C、D、E、F或者是A、B、D、C、E、F(结果并不一定是唯一的)
实现思路
- 可以使用卡恩算法(kahn于1962年提出)完成拓扑排序
- 假设L是存放拓扑排序结果的列表
- 把所有入度为0的顶点放入L中,然后把这些顶点从图中去掉
- 重复操作1,直到找不到入度为0的顶点
- 如果此时L中的元素个数和顶点总数相同,说明拓扑排序完成
- 如果此时L中的元素个数少于顶点总数,说明原图中存在环,无法进行拓扑排序
实现过程
-
首先维护一张入度表和一个list集合,和一个队列
入度表
队列(初始化将所有的入度为0的顶点放到队列中)
-
将C出队,放到list中,并且遍历所有以C为为起点的边的终点的集合,将这些点的入度-1,如果这些点中存在-1之后入度为0的点,将其加入到队列中。
-
将队列中的顶点E出队,放到list中,并且遍历所有以E为为起点的边的终点的集合,将这些点的入度-1, 此轮-1后,点A的入度为0,入队
-
A点出队,放到list集合中,以A点为起点的边的终点集合的入度-1,此轮之后,B,D两点的入度为0,加入到队列中
-
D点出队,加入list集合中
-
B点出队,加入到list集合中,更新入度表,点F入队
- 点F出队,加入到list集合中
代码
- 图的接口类
public interface Graph<V, E> {
/**
* 打印边
*/
void print();
/**
* 边的数量
* @return
*/
int edgeSize();
/**
* 顶点的数量
* @return
*/
int vertexSize();
/**
* 添加顶点
* @param v
*/
void addVertex(V v);
/**
* 添加边(无权值)
* @param from
* @param to
*/
void addEdge(V from, V to);
/**
* 添加边(有权值)
* @param from
* @param to
* @param weight
*/
void addEdge(V from, V to, E weight);
/**
* 移除顶点
* @param v
*/
void removeVertex(V v);
/**
* 移除边
* @param from
* @param to
*/
void removeEdge(V from, V to);
/**
* 拓扑排序
* @return
*/
List<V> topologicalSort();
}
- 图的实现类
public class ListGraph<V, E> implements Graph<V, E> {
private Map<V, Vertex<V, E>> vertices = new HashMap<>();
private Set<Edge<V, E>> edges = new HashSet<>();
/**
* 打印
*/
@Override
public void print() {
//打印顶点
vertices.forEach((v, vertex) -> {
System.out.println(v);
System.out.println("out-----------");
System.out.println(vertex.outEdges);
System.out.println("in------------");
System.out.println(vertex.inEdges);
}