10.1 图的定义和相关术语
10.2 图的两种存储方式
10.3 图的遍历
DFS例题:
1034 Head of a Gang
分数 30
全屏浏览题目
切换布局
作者 CHEN, Yue
单位 浙江大学
One way that the police finds the head of a gang is to check people's phone calls. If there is a phone call between A and B, we say that A and B is related. The weight of a relation is defined to be the total time length of all the phone calls made between the two persons. A "Gang" is a cluster of more than 2 persons who are related to each other with total relation weight being greater than a given threshold K. In each gang, the one with maximum total weight is the head. Now given a list of phone calls, you are supposed to find the gangs and the heads.
Input Specification:
Each input file contains one test case. For each case, the first line contains two positive numbers N and K (both less than or equal to 1000), the number of phone calls and the weight threthold, respectively. Then N lines follow, each in the following format:
Name1 Name2 Time
where Name1 and Name2 are the names of people at the two ends of the call, and Time is the length of the call. A name is a string of three capital letters chosen from A-Z. A time length is a positive integer which is no more than 1000 minutes.
Output Specification:
For each test case, first print in a line the total number of gangs. Then for each gang, print in a line the name of the head and the total number of the members. It is guaranteed that the head is unique for each gang. The output must be sorted according to the alphabetical order of the names of the heads.
Sample Input 1:
8 59
AAA BBB 10
BBB AAA 20
AAA CCC 40
DDD EEE 5
EEE DDD 70
FFF GGG 30
GGG HHH 20
HHH FFF 10
Sample Output 1:
2
AAA 3
GGG 3
Sample Input 2:
8 70
AAA BBB 10
BBB AAA 20
AAA CCC 40
DDD EEE 5
EEE DDD 70
FFF GGG 30
GGG HHH 20
HHH FFF 10
Sample Output 2:
0
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
代码:
import java.util.Comparator;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Set;
/**
* 1.解决节点名字与编号的映射关系:利用两个HashMap建立双向映射
* 2.使用邻接矩阵记录节点与节点之间的边权关系
* 3.单开一个数组记录各节点的点权
* 4.DFS访问各个连通块并做vis标记,访问时统计连通块总边权、总节点数、最大点权节点
* 统计总边权时要注意如测试案例中gang3的有环图,解决思想是先累加边权,再根据vis标记递归访问
* 5.题目输出格式要按gang leader名字的字典序输出,可以把hp的keySet排下序
*/
public class PAT_A1034_2 {
static int n;//总边数
static int k;
final static int maxn=2020;
static int[][] g=new int[maxn][maxn];
static int sumPerson;//实际总人数(优化查找时间复杂度)
static HashMap<String,Integer> hpStringToInt=new HashMap<>();//映射关系
static HashMap<Integer,String> hpIntToString=new HashMap<>();
static int[] vertexWeight=new int[maxn];
static boolean[] vis=new boolean[maxn];
//dfs需要更新:
static int sumEdge;//连通块总边权
static int sumVertex;//连通块总节点数
static int maxWeightVertex;//连通块中点权最大的节点
static int maxWeight;//连通块中的最大点权
//记录gang
static int gangSum;
static HashMap<Integer,Integer> hp=new HashMap<>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n= scanner.nextInt();
k= scanner.nextInt();
for(int i=0;i<n;i++){
String s1= scanner.next();
String s2= scanner.next();
int edgeWeight= scanner.nextInt();
//建立映射关系
int vertex1 = createMapping(s1);
int vertex2 = createMapping(s2);
//记录边权关系
g[vertex1][vertex2]+=edgeWeight;//因为是无向图,所以两个一个不可少
g[vertex2][vertex1]+=edgeWeight;//因为是无向图,所以两个一个不可少
//记录点权:
vertexWeight[vertex1]+=edgeWeight;
vertexWeight[vertex2]+=edgeWeight;
}
//DFS访问各连通块
for (int i=0;i<sumPerson;i++){
if(vis[i]==false){
//dfs()访问连通块
sumEdge=0;
sumVertex=1;
maxWeightVertex=i;
maxWeight=vertexWeight[i];
dfs(i);
if(sumEdge>k&&sumVertex>2){//算作一个gang
gangSum++;
hp.put(maxWeightVertex,sumVertex);
}
}
}
//输出结果
System.out.println(gangSum);
Set<Integer> keys = hp.keySet();
keys.stream().sorted(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
for (Integer key : keys) {
String name=hpIntToString.get(key);
System.out.println(name+" "+hp.get(key));
}
}
//TODO 涉及统计无向图总边权
public static void dfs(int vertex){
vis[vertex]=true;
for(int i=0;i<sumPerson;i++){//如果不记录总人数sumPerson则需要使用maxn,会大大增加时间复杂度
if(g[vertex][i]!=0){
sumEdge+=g[vertex][i];//TODO 先累加边权
g[vertex][i]=0;
g[i][vertex]=0;
if(vis[i]==false){//TODO 再递归访问
sumVertex++;
if(vertexWeight[i]>maxWeight){//TODO 这里最开始写的有问题,vertexWeight[i]不应该与vertexWeight[vertex]比,而是与maxWeight比
maxWeightVertex=i;
maxWeight=vertexWeight[i];
}
dfs(i);
}
}
}
}
public static int createMapping(String s){//TODO 建立双向映射
if(!hpStringToInt.containsKey(s)){//未存放过
hpStringToInt.put(s,sumPerson);
hpIntToString.put(sumPerson,s);
return sumPerson++;//更新实际人数
}else{//已经存放过了
return hpStringToInt.get(s);
}
}
}
BFS例题
1076 Forwards on Weibo
分数 30
全屏浏览题目
切换布局
作者 CHEN, Yue
单位 浙江大学
Weibo is known as the Chinese version of Twitter. One user on Weibo may have many followers, and may follow many other users as well. Hence a social network is formed with followers relations. When a user makes a post on Weibo, all his/her followers can view and forward his/her post, which can then be forwarded again by their followers. Now given a social network, you are supposed to calculate the maximum potential amount of forwards for any specific user, assuming that only L levels of indirect followers are counted.
Input Specification:
Each input file contains one test case. For each case, the first line contains 2 positive integers: N (≤1000), the number of users; and L (≤6), the number of levels of indirect followers that are counted. Hence it is assumed that all the users are numbered from 1 to N. Then N lines follow, each in the format:
M[i] user_list[i]
where M[i] (≤100) is the total number of people that user[i] follows; and user_list[i] is a list of the M[i] users that followed by user[i]. It is guaranteed that no one can follow oneself. All the numbers are separated by a space.
Then finally a positive K is given, followed by K UserID's for query.
Output Specification:
For each UserID, you are supposed to print in one line the maximum potential amount of forwards this user can trigger, assuming that everyone who can view the initial post will forward it once, and that only L levels of indirect followers are counted.
Sample Input:
7 3
3 2 3 4
0
2 5 6
2 3 1
2 3 4
1 4
1 5
2 2 6
Sample Output:
4
5
代码长度限制
16 KB
时间限制
3000 ms
内存限制
64 MB
代码:
package 第十章.BFS;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
import java.util.Vector;
public class PAT_A1076 {
static int n;//用户个数
static int l;//最大转发层数
final static int maxn=1000;
static Vector<Node>[] g=new Vector[maxn];//邻接矩阵存储有向图
static int[] beginNode;//记录起始节点编号
static boolean[] inq=new boolean[maxn];
static int records;//记录结果
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n= scanner.nextInt();
l= scanner.nextInt();
for(int i=0;i<maxn;i++){
g[i]=new Vector<>();
}
for (int i=1;i<=n;i++){//注意用户编号是从1开始
int sum= scanner.nextInt();
Node node = new Node();
node.num=i;
for (int j=0;j<sum;j++){
int follow= scanner.nextInt();
g[follow].add(node);
}
}
int begins= scanner.nextInt();
beginNode=new int[begins];
for(int i=0;i<begins;i++){
beginNode[i]= scanner.nextInt();
}
//上面已经读取完毕并建立有向图
/**
* 要求在转发层数上限内消息最多会被多少用户转发--->求bfs路过的边数
* bfs
*/
for(int i=0;i<begins;i++){
Node node = new Node();
node.num=beginNode[i];
records=0;
bfs(node);
for(int j=1;j<=n;j++){//不影响再次dfs //注意用户序号是从1开始的
inq[j]=false;
}
System.out.println(records);
}
}
public static void bfs(Node node){
node.depth=0;
Queue<Node> queue=new LinkedList<>();
queue.offer(node);
inq[node.num]=true;
while(!queue.isEmpty()){
Node temp = queue.poll();
int curDepth= temp.depth;
if(curDepth==l){//不能超过层数限制
break;
}
for(int i=0;i<g[temp.num].size();i++){
if(inq[g[temp.num].get(i).num]==false){
records++;//转发的用户+1
inq[g[temp.num].get(i).num]=true;
g[temp.num].get(i).depth=curDepth+1;
queue.offer(g[temp.num].get(i));
}
}
}
}
}
class Node{
int num;//序号,从1开始
int depth;//层数
}
10.4 最短路径问题
对任意给出的图G(V,E)和起点S、终点T,如何求从S到T的最短路径
1. Dijkstra算法
解决单源最短路径问题:给定图G和起点s,通过算法得到S到达其他每个顶点的最短距离
Dijkstra算法只能应对所有边权都是非负数的情况,如果边权出现负数,那么Dijkstra算法很可能出错,这时最好使用SPFA算法
通过pre[v]记录最短路径
碰到有两条及以上可以达到最短距离的路径,题目就会给出一个第二标尺(第一标尺距离),要求在所有最短路径中选择第二标尺最优的一条路径。第二标尺:边权、点权、最短路径条数
例题:
1003 Emergency
分数 25
全屏浏览题目
切换布局
作者 CHEN, Yue
单位 浙江大学
As an emergency rescue team leader of a city, you are given a special map of your country. The map shows several scattered cities connected by some roads. Amount of rescue teams in each city and the length of each road between any pair of cities are marked on the map. When there is an emergency call to you from some other city, your job is to lead your men to the place as quickly as possible, and at the mean time, call up as many hands on the way as possible.
Input Specification:
Each input file contains one test case. For each test case, the first line contains 4 positive integers: N (≤500) - the number of cities (and the cities are numbered from 0 to N−1), M - the number of roads, C1 and C2 - the cities that you are currently in and that you must save, respectively. The next line contains N integers, where the i-th integer is the number of rescue teams in the i-th city. Then M lines follow, each describes a road with three integers c1, c2 and L, which are the pair of cities connected by a road and the length of that road, respectively. It is guaranteed that there exists at least one path from C1 to C2.
Output Specification:
For each test case, print in one line two numbers: the number of different shortest paths between C1 and C2, and the maximum amount of rescue teams you can possibly gather. All the numbers in a line must be separated by exactly one space, and there is no extra space allowed at the end of a line.
Sample Input:
5 6 0 2
1 2 1 5 3
0 1 1
0 2 2
0 3 1
1 2 1
2 4 1
3 4 1
Sample Output:
2 4
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
代码:
package 第十章.最短路径问题;
import java.util.Scanner;
/**
* 有个问题?
* 邻接表怎么记录边权?
* 把边权记录到节点里,节点有两个属性,一个是编号,另一个是边权
*/
/**
* 保证最短距离的同时获取最大点权
* 1.建图:无向图,带环,每个节点有权重,有边权--->邻接矩阵,初始化边权为INF
* 2.常规dijkstra,更新最短路径时更新最大点权--->需要开辟一个数组记录点权,另一个数组记录起点s到每个点的最大点权和
* 3.点的序号从0开始到n-1
*/
public class PAT_A1003 {
final static int MAXN=501;
final static int INF=100000001;
static int n;//节点个数
static int m;//边数
static int c1;//起点
static int c2;//终点
static int[] vertexWeight=new int[MAXN];//记录点权
static int[][] g=new int[MAXN][MAXN];//记录边权
//Dijkstra:
static boolean[] vis=new boolean[MAXN];
static int[] d=new int[MAXN];
static int[] sumVerWeight=new int[MAXN];//记录最大点权和 //记得初始化
static int[] shortPaths=new int[MAXN];//记录最短路径条数 //记得初始化
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n= scanner.nextInt();
m= scanner.nextInt();
c1= scanner.nextInt();
c2= scanner.nextInt();
for(int i=0;i<n;i++){
vertexWeight[i]= scanner.nextInt();
}
for(int i=0;i<MAXN;i++){
for(int j=0;j<MAXN;j++){
g[i][j]=INF;
}
}
for (int i=0;i<m;i++){
int v1= scanner.nextInt();
int v2= scanner.nextInt();
int length= scanner.nextInt();
g[v1][v2]=length;
g[v2][v1]=length;//TODO 他妈的无向图这里不要忘了
}
//以上建图完毕
/**
* 则通过Dijkstra求出最短路径个数、求出最短路径时的最大点权
*/
dijkstra(c1);
System.out.print(shortPaths[c2]+" ");
System.out.print(sumVerWeight[c2]);
}
public static void dijkstra(int start){
//Dijkstra初始化操作
for(int i=0;i<MAXN;i++){
d[i]=INF;
}
d[start]=0;
for(int i=0;i<MAXN;i++){
sumVerWeight[i]=0;
shortPaths[i]=0;
}
sumVerWeight[start]=vertexWeight[start];
shortPaths[start]=1;
//初始化完成
for (int i=0;i<n;i++){
int u=-1;
int MIN=INF;
for(int j=0;j<n;j++){
if(vis[j]==false&&d[j]<MIN){
u=j;
MIN=d[j];
}
}
if(u==-1) return;
vis[u]=true;
for(int v=0;v<n;v++){
if(vis[v]==false&&g[u][v]!=INF){
if(d[u]+g[u][v]<d[v]){
d[v]=d[u]+g[u][v];
shortPaths[v]=shortPaths[u];//TODO 记得更新最短路径条数
sumVerWeight[v]=sumVerWeight[u]+vertexWeight[v];//TODO 记得更新点权和
}else if((d[u]+g[u][v]==d[v])){//TODO 当d[u]+g[u][v]==d[v]时,无论是否sumVerWeight[u]+vertexWeight[v]>sumVerWeight[v],都应当让shortPaths[v]+=shortPaths[u],因为最短路径条数
//TODO 的依据仅仅是第一标尺的距离,与点权无关
shortPaths[v]+=shortPaths[u];
if(sumVerWeight[u]+vertexWeight[v]>sumVerWeight[v]){
sumVerWeight[v]=sumVerWeight[u]+vertexWeight[v];
}
}
}
}
}
}
}
Dijkstra+DFS:
先在Dijkstra算法中记录下所有最短的路径(只考虑距离,然后从这些最短路径中选出一条第二标尺最优的路径)
步骤:
- 使用Dijkstra算法记录所有最短路径
由于要记录所有最短路径,因此每个节点就会存在多个前驱节点。不妨把pre数组定义为Vector类型 Vector pre[ MAXV]。在之前的写法中,pre[i]被初始化为i,表示每个节点在初始状态下的前驱是自身。但是在此处,pre数组一开始不需要赋初值。
考虑更新d[v]的过程中pre数组的变化。两种情况:d[u]+g[u][v]<d[v]时记得清空pre[v]再添加u作为前驱节点,d[u]+g[u][v]==d[v]时直接添加u作为前驱节点
以上便完成了pre数组的求解
- 遍历所有最短路径,找出一条使第二标尺最优的路径
遍历所有路径要使用递归,由于每个节点的前驱节点可能有多个,遍历的过程就会形成一颗递归树,对这颗树进行遍历的时候,每次到达叶子节点,就会产生一条完整的最短路径,就可以对这条路径计算其第二标尺的值,将该值与最优值比较,如果当前值比最优值更优,则更新最优值,并用这条路径覆盖当前的最优路径
如何写DFS的递归函数?
需要有的是:
1.作为全局变量的第二标尺最优值optValue 2.记录最优路径的数组path(使用Vector存储) 3.临时记录DFS遍历到叶子节点(路径的起点)时的路径tempPath(使用Vector存储)
最后的问题便是如何在递归过程中生成tempPath,与全排列十分相似。最后生成的tempPath中的路径节点是逆序的,因此访问节点需要倒着进行,但这不影响路径上的边权、点权求和。
例题: 1030 Travel Plan
分数 30
全屏浏览题目
切换布局
作者 CHEN, Yue
单位 浙江大学
A traveler's map gives the distances between cities along the highways, together with the cost of each highway. Now you are supposed to write a program to help a traveler to decide the shortest path between his/her starting city and the destination. If such a shortest path is not unique, you are supposed to output the one with the minimum cost, which is guaranteed to be unique.
Input Specification:
Each input file contains one test case. Each case starts with a line containing 4 positive integers N, M, S, and D, where N (≤500) is the number of cities (and hence the cities are numbered from 0 to N−1); M is the number of highways; S and D are the starting and the destination cities, respectively. Then M lines follow, each provides the information of a highway, in the format:
City1 City2 Distance Cost
where the numbers are all integers no more than 500, and are separated by a space.
Output Specification:
For each test case, print in one line the cities along the shortest path from the starting point to the destination, followed by the total distance and the total cost of the path. The numbers must be separated by a space and there must be no extra space at the end of output.
Sample Input:
4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20
Sample Output:
0 2 3 3 40
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
代码:
package 第十章.最短路径问题.dijkstraAndDfs;
import java.util.Scanner;
import java.util.Vector;
/**
* 1.双重标尺:最短路径+最小边权
* 2.顶点序号0~n-1
* 3.求双重标尺下的路径+距离+边权
* 4.无向图
*
* 求解:
* 1.邻接矩阵记录距离
* 2.开二维数组记录边权
* 3.dijkstra+dfs
*/
public class PAT_A1030 {
static int n;//节点个数
static int m;//边个数
static int s;
static int d;
final static int MAXN=510;
final static int INF=10000001;
static int[][] g=new int[MAXN][MAXN];//距离
static int[][] edgeWeight=new int[MAXN][MAXN];//边权
//dijkstra:
static int[] dis=new int[MAXN];
static boolean[] vis=new boolean[MAXN];
static Vector<Integer>[] pre=new Vector[MAXN];
//dfs:
static Vector<Integer> tempPath=new Vector<>();
static Vector<Integer> optPath=new Vector<>();
static int optValue=INF;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n= scanner.nextInt();
m= scanner.nextInt();
s= scanner.nextInt();
d= scanner.nextInt();
for(int i=0;i<MAXN;i++){
for(int j=0;j<MAXN;j++){
g[i][j]=INF;
edgeWeight[i][j]=INF;
}
}
for(int i=0;i<m;i++){
int v1= scanner.nextInt();
int v2= scanner.nextInt();
int distance= scanner.nextInt();
int weight= scanner.nextInt();
g[v1][v2]=distance;
g[v2][v1]=distance;
edgeWeight[v1][v2]=weight;
edgeWeight[v2][v1]=weight;
}
//图初始化完毕
//Dijkstra记录下所有最短路径:
dijkstra(s);
//dfs遍历所有最短路径,筛选出边权最小的:
dfs(d);
//输出结果:
for(int i=optPath.size()-1;i>=0;i--){
System.out.print(optPath.get(i)+" ");
}
System.out.print(dis[d]+" ");
System.out.print(optValue);
}
public static void dfs(int des){
if(des==s){
tempPath.add(des);
int tempValue=0;
for (int i=0;i< tempPath.size()-1;i++){
int v1 = tempPath.get(i);
int v2 = tempPath.get(i+1);
tempValue+=edgeWeight[v1][v2];
}
if(tempValue<optValue){
optValue=tempValue;
optPath.clear();
for (Integer integer : tempPath) {//深拷贝
optPath.add(integer);
}
}
tempPath.remove((Object)des);//TODO 这里不强制类型转换的话,集合会默认是按index删除元素
return;
}
tempPath.add(des);
for(int i=0;i<pre[des].size();i++){
dfs(pre[des].get(i));
}
tempPath.remove((Object)des);
}
public static void dijkstra(int start){
//dijkstra初始化
for(int i=0;i<MAXN;i++){
dis[i]=INF;
pre[i]=new Vector<>();
}
dis[start]=0;
for(int i=0;i<n;i++){
int u=-1;
int MIN=INF;
for(int j=0;j<n;j++){
if(vis[j]==false&&dis[j]<MIN) {
u = j;
MIN = dis[j];
}
}
if(u==-1) return;
vis[u]=true;
for(int v=0;v<n;v++){
if(vis[v]==false&&g[u][v]!=INF){
if(dis[u]+g[u][v]<dis[v]){
dis[v]=dis[u]+g[u][v];
pre[v].clear();//一定要记得clear掉
pre[v].add(u);
}else if(dis[u]+g[u][v]==dis[v]){
pre[v].add(u);
}
}
}
}
}
}
2.Bellman-Ford算法,简称BF算法
与Dijkstra算法一样,可解决单源最短路径问题,但也能处理有负权边的情况
出现了负权边,Dijkstra算法会失效
考虑环:将环分为零环、正环、负环
零环和正环不会影响最短路径的求解,因为零环和正环的存在不能使最短路径更短;而如果图中有负环,且从源点可以到达,那么就会影响最短路径的求解;但如果图中的负环无法从源点出发到达,则最短路径的求解不会受到影响
与Dijkstra算法相同,BF同样设置一个数组d,用来存放从源点到达各个顶点的最短距离。同时BF算法返回一个bool值;如果其中存在从源点可到达的负环,那么函数将返回false,否则返回true,此时数组d中存放的就是从源点到达各顶点的最短距离
由于Bellman-Ford算法需要遍历所有边,显然使用邻接表会更加方便。
对于BF算法:最短路径的求解方法、有多重标尺时的做法均与Dijkstra算法中介绍的相同。唯一要注意的是统计最短路径条数的做法:由于BF算法期间会反复累计已经计算过的顶点。为了解决这个问题,当遇到一条和已有最短路径长度相同的路径时,必须重新计算最短路径条数。
3.SPFA算法
SPFA是由BF优化而来,使用了队列
暂时理解不了是如何优化得到的SPFA,看不懂
4.Floyd算法
用来解决全源最短路问题,即对给定的图G(V,E),求任意两点u,v之间的最短路径长度,适合于邻接矩阵
10.5 最小生成树(MST)
是在无向图的基础上生成的
对于给定的图,最小生成树可以不唯一,但其边权之和一定是唯一的
求解最小生成树一般有两种算法,prim和kruskal,两个算法都是采用贪心法的思想,只是贪心的策略不太一样
-
prim算法
prim算法的思想与Dijkstra算法的思想几乎完全相同,只是在涉及最短距离时使用了集合S代替Dijkstra算法中的起点s。两者只有在数组d[]的含义上有区别,并且在优化d[v]的部分不同
尽量在稠密图上使用prim算法,因为时间复杂度主要来源于遍历顶点
-
kruskal算法
采用了边贪心的策略
由于需要判断测试边的两个端点是否在不同的连通块中和需要考虑如何将测试边加入到最小生成树中,故利用并查集
尽量在稀疏图上使用kruskal算法,因为时间复杂度主要来源于对边排序
10.6 拓扑排序
有向无环图(DAG)
拓扑排序、拓扑序列
求解拓扑序列:邻接表、队列、数组inDegree[]记录节点的入度
拓扑排序成功:图G为DAG,否则图中有环
boolean topologicalSort()拓扑排序的很重要的应用就是判断一个给定的图是否是DAG
如果要求有多个入度为0的顶点,选择编号最小的顶点,那么可以使用优先队列(小顶堆)来代替普通队列
10.7 关键路径
AOV网和AOE网,都是DAG
AOV:顶点表示活动,边集表示活动间优先关系
AOE:带权的边集表示活动,顶点表示事件,其中边权表示完成活动需要的时间。"事件"仅表示一个中介状态。一般来说,AOE网用来表示一个工程的进行过程
超级源点与超级汇点
如果给定AOV网中各顶点活动所需要的时间,那么就可以将AOV网转换为AOE网
弹性时间
关键路径就是AOE网的最长路径,等于整个工程的最短完成时间。把关键路径上的活动称为关键活动。
求解最长路径:
(宏观上)对于一个没有从源点可到达的正环的图,如果需要求最长路径长度,则可以把所有边的边权乘以-1,令其变为相反数,然后使用BF或者SPFA算法求解最短路径长度,将所得结果取反即可。
(对于DAG),求最长路径就是求关键路径长度(针对AOE网)
求解关键路径:(针对AOE网) 实际上求解的就是最长路径
思路:先求点,再夹边
点指的是事件,边指的是活动
ve数组:事件最早开始时间
vl数组:事件最晚开始时间
e数组:活动最早开始时间
l数组:活动最晚开始时间
e、l数组与ve、vl数组之间存在函数关系,要求e、l则先求ve、vl
1.1 拓扑排序,利用Stack记录下拓扑序列并且求出ve数组 1.2 初始化vl数组,初始值为终点的ve值 1.3 将stack中元素pop,可以得到逆拓扑序列,求解vl数组 1.4 可根据ve数组与vl数组求得e数组与l数组 1.5 e[i]==l[i]则说明活动i为关键活动,ve数组中最大的就是最长路径长(关键路径长、最短时间)
如果图中有多条关键路径,并且要完整输出所有关键路径,就需要把关键活动存下来,方法就是新建一个邻接表,当确定边u->v是关键活动时,将边u->v加入邻接表。这样最后生成的邻接表就是所有关键路径合成的图了,可以使用DFS遍历来获取所有关键路径。