拓扑排序
介绍
1、对一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前
2、这样的线性序列称为满足拓扑次序的序列,简称拓扑序列
3、只有有向无环图才存在拓扑序列
4、对于一个有向无环图,可能存在多个拓扑序列
概念
1、DGA:有向无环图
2、AOV网
(1)无权有向无环图
(2)顶点表示活动,边表示活动(顶点)发生的先后关系
(3)AOV 网的边不设权值,若存在边 <a,b>,则表示活动 a 必须发生在活动 b 之前
(4)若网中所有活动均可以排出先后顺序(任两个活动之间均确定先后顺序),则称网是拓扑有序的,这个顺序称为网上一个全序
(5)数据在顶点,可以理解为面向对象
3、AOE网
(1)带权有向无环图
(2)边代表活动,顶点代表所有指向它的边所代表的活动均已完成这一事件
(3)由于整个工程只有一个起点和一个终点,网中只有一个入度为 0 的点(源点)和一个出度为 0 的点(汇点)
(4)数据在边上,可以理解为面向过程
(5)“发生”是针对于事件的,也就是图中的顶点。什么叫事件发生了呢?
AOE 发生 / 开始 时间
1、发生
(1)针对事件,即图中的顶点
(2)只有在指向该顶点的所有有向边对应的活动结束,该顶点所代表的事件才发生
(3)例:一个事件 C,它仅被两条边 a、b 指向,仅当 a、b 两活动都完成时,事件 C 发生
2、开始
(1)针对活动,即图中的边
(2)只有在一个顶点所代表的事件发生后,从该顶点出发的所有边对应的活动才能开始
(3)既可以在事件一完成就立马开始接下来的活动,也可以推迟活动开始的时间
3、关键路径
(1)整个活动的完成时间是 AOE 图中从始点到终点的最长路径的长度
(2)关键路径上的活动称作关键活动
(3)关键路径不一定只有一条
4、事件最早发生时间 ve = Max{前驱顶点最早开始时间 + 活动权值}
(1)之前所有活动均完成所需的时间,由耗时最长的路径决定
(2)Max{目标顶点的所有前提顶点的最早完成时间 + 对应的前提顶点到目标顶点的活动消耗时间},如:Max{A 最早完成时间 + A 到 C 的活动消耗时间, B 最早完成时间 + B 到 C 的活动消耗时间}
(3)递推公式:从源点开始一直递推到汇点(终点)
ve(源点) = 0
ve(k) = Max{ve{j} + Weight(j, k) }
j 为 k 的任意前驱顶点, Weight(j, k)表示 <j, k> 上的权值
5、事件的最迟发生时间 vl = Min{后继顶点最晚开始时间 - 活动权值}
(1)在不推迟整个工程完成的前提下,保证后继事件 j 能在其最迟发生时间 vl(j) 能够发生时,该事件最迟必须发生的时间
(2)一个顶点的最迟发生时间由它的后继节点所决定
(3)递推公式:从汇点开始一直递推到源点(起点)
vl(汇点) = ve(汇点)
vl(k) = Min{vl(j) - Weight(k, j)}
k为j的任意前驱
6、活动的最早开始时间 e = 前驱顶点最早开始时间
(1)边的起点时间,为其起点事件的最早发生时间
(2)若边 <k, j> 表示活动 i,则有 e(i) = ve(k)
7、活动的最迟开始时间 l = 后继顶点最晚开始时间 - 活动权值
(1)边所指向的事件(终点)最迟发生时间 - 边对应的活动所需时间
(2)若边 <k, j> 表示活动 i,则有 l(i) = vl(j) - Weight(k, j)
逆拓扑排序
1、从有向图中选取一个没有直接的后继节点(即出度为 0)的顶点,并输出
2、从有向图中删去此顶点以及指向它的有向边(入度)
3、重复步骤1、2,产生两种结果
(1)全部顶点均已输出,逆拓扑序列形成,逆拓扑排序完成
(2)或图中还有未输出的顶点,但已跳出处理循环,说明图中还剩下一些顶点,它们都有直接后继节点(即出度不为0),有向无环图中必定存在有向环
4、本质:深度优先
拓扑排序
1、从有向图中选取一个没有直接的前驱节点(即入度为 0)的顶点,并输出
2、从有向图中删去此顶点以及它发出的有向边(出度)
3、重复步骤1、2,产生两种结果
(1)全部顶点均已输出,拓扑有序序列形成,拓扑排序完成
(2)或图中还有未输出的顶点,但已跳出处理循环,说明图中还剩下一些顶点,它们都有直接前驱节点(即入度不为0),有向无环图中必定存在有向环
4、本质:广度优先
5、代码实现
import java.util.LinkedList;
public class Digraph {//不带权有向图
public char[] vertex;//储存顶点
public int[][] matrix;//邻接矩阵:0代表无边;1代表顶点的出度
public int[] indegree;//下标对应顶点,储存顶点的入度个数
public LinkedList<Character> result;//储存拓扑排序后的顶点
//测试拓扑排序
public static void main(String[] args) {
//有向有环图
char[] vertex1 = {'A', 'B', 'C'};
int[][] matrix1 = {
{0, 1, 0},
{0, 0, 1},
{1, 0, 0}
};
Digraph digraph1 = new Digraph(vertex1, matrix1);
digraph1.topologySort();
//有向无环图
char[] vertex2 = {'A', 'B', 'C', 'D', 'E'};
int[][] matrix2 = {
{0, 0, 1, 0, 0},
{1, 0, 0, 1, 0},
{0, 0, 0, 0, 1},
{1, 0, 0, 0, 1},
{0, 0, 0, 0, 0}
};
Digraph digraph2 = new Digraph(vertex2, matrix2);
digraph2.topologySort();
}
public Digraph(char[] vertex, int[][] matrix) {
this.vertex = vertex;
this.matrix = matrix;
this.result = new LinkedList<>();
this.indegree = new int[vertex.length];
//统计初始情况时,各顶点入度个数
for (int i = 0; i < vertex.length; i++) {
for (int j = 0; j < vertex.length; j++) {
if (matrix[i][j] == 1) {
indegree[j] += 1;
}
}
}
}
//拓扑排序
public void topologySort() {
//临时储存入度为0的顶点下标
LinkedList<Integer> temp = new LinkedList<>();
//初始化temp,储存图的入度为0的顶点下标
for (int i = 0; i < vertex.length; i++) {
if (indegree[i] == 0) {
temp.addLast(i);
}
}
//循环直到没有入度为0的顶点为止
while (!temp.isEmpty()) {
//取出入度为0的顶点下标index
int index = temp.removeFirst();
//将index顶点放入result末尾
result.addLast(vertex[index]);
for (int i = 0; i < vertex.length; i++) {//
//删除其他顶点(与index顶点有关)的入度
if (matrix[index][i] == 1) {
indegree[i]--;
//若该顶点删除(与index顶点有关)入度后,该顶点入度为0,则将其加入temp末尾
if (indegree[i] == 0) {
temp.addLast(i);
}
}
}
}
if (result.size() == vertex.length) {//所有顶点都能取出
print();
} else {//result.size() < vertex.length,说明有环,不能把所有顶点取出
System.out.println("有向图存在环,无法拓扑排序");
}
}
//输出拓扑排序结果
public void print() {
for (Character character : result) {
System.out.print(character + "\t");
}
System.out.println();
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战