一、求任意两点之间的最短路径——Floyd--Warshall
此算法的思想是判断顶点i到顶点j的距离是否会比绕过顶点c的距离要小,如果小,就更新距离。
得到的是任意两点之间的最短路径,可以解决负权边的情况,不可以解决负权环。时间复杂度是O(N^3)
输入:
4 8 //n个顶点,m条边
1 2 2 //从顶点1到顶点2的路程是2
1 3 6
1 4 4
2 3 3
3 1 7
3 4 1
4 1 5
4 3 12
输出:
顶点1 顶点2 顶点3 顶点4
顶点1 0 2 6 4
顶点2 10 0 3 99999999
顶点3 6 99999999 0 1
顶点4 5 99999999 12 0
代码:
import java.util.Scanner;
public class Floy_Warshall {
public static void main(String[] args) {
int[][] e = new int[10][10]; //矩阵保存点到点的距离
int n,m,t1,t2,t3;
int inf = 99999999;//假设inf = 9999999为无穷大
Scanner sc = new Scanner(System.in);
n = sc.nextInt(); //顶点数
m = sc.nextInt(); //边的条数
//初始化,规定点到自身的距离为0,点到其他点的距离为无穷大
for(int i=1;i<=n; i++)
for(int j=1; j<=n; j++){
if(i==j) e[i][j] =0;
else e[i][j] = inf;
}
//读入边,从点t1到t2的距离为t3
for(int i=1;i<=m; i++){
t1 = sc.nextInt();
t2 = sc.nextInt();
t3 = sc.nextInt();
e[t1][t2] = t3;
}
//对顶点到顶点的距离进行松弛,
for(int k=1; k<=n; k++){
for(int i=1; i<=n; i++)
for(int j=1; j<=1; j++){
if(e[i][j] > e[i][k]+e[k][j])
e[i][j] = e[i][k] + e[k][j];
}
}
//打印输出,输出的是顶点i到顶点j的最短距离
for(int i =1; i<=n;i++){
for(int j=1; j<=n; j++)
System.out.print(e[i][j] + " ");
System.out.println();
}
sc.close();
}
}
二、求单源最短路径——Dijkstra
此算法可以用来求一个点(源点)到其余各个顶点的最短路径,也叫做单源最短路径。
算法的思想是:每次找到离源点最近的一个顶点,然后以该顶点为中心进行扩展,最终会得到源点到其余所有点的最短路径。
不能解决负权边。时间复杂度是O((M+N)logN)
输入:
6 9 //n个顶点m条边
1 2 1 //顶点1到顶点2的距离为1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
输出:
//顶点1到各顶点的最短路径
0 1 8 4 13 17
代码:
import java.util.Scanner;
public class Dijkstra {
public static void main(String[] args) {
int[][] e = new int[10][10]; //存储点到点的距离
int[] book = new int[10]; //标记数组,记录哪个顶点在找到最短路程的集合中
int[] dis = new int[10]; //存储顶点1到各顶点的距离
int n,m,t1,t2,t3,min,u = 0,i,j;
int inf = 99999999;//假设inf = 9999999为无穷大
Scanner sc = new Scanner(System.in);
n = sc.nextInt(); //顶点数
m = sc.nextInt(); //边的条数
//初始化,规定点到自身的距离为0,点到其他点的距离为无穷大
for(i=1;i<=n; i++)
for( j=1; j<=n; j++){
if(i==j) e[i][j] =0;
else e[i][j] = inf;
}
//读入边,从点t1到t2的距离为t3
for(i=1;i<=m; i++){
t1 = sc.nextInt();
t2 = sc.nextInt();
t3 = sc.nextInt();
e[t1][t2] = t3;
}
//初始化顶点1到各个顶点的距离
for( i=1; i<=n; i++)
dis[i] = e[1][i];
//储数化标记数组
for(i=1; i<=n; i++)
book[i] = 0;
book[1] =1;//顶点1已经在找到最短路径的集合中
//找到离顶点1最近的点
for(i=1; i<=n-1; i++){
min = inf;
for( j=1; j<=n; j++){
if(book[j] == 0 && dis[j] < min){
min = dis[j];
u = j;
}
}
//对离顶点1最近的点做处理
book[u] = 1;
for(int v=1; v<=n; v++){
if(e[u][v] < inf){
//更新顶点1到其他顶点v的距离
if(dis[v] > dis[u] + e[u][v])
dis[v] = dis[u] + e[u][v];
}
}
}
//打印输出
for(i=1; i<=n; i++){
System.out.print(dis[i] + " ");
}
sc.close();
}
}
三、Bellman-Ford——解决负权边
时间复杂度是O(NM)
核心代码:
for(i=1;i<=m;i++)
if(dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i]
代码的意思是,看看能否通过u[i]-->v[i](权值为w[i0}这条边,使得顶点1到顶点v[i]顶点的距离变短。循环把所有的边松弛一遍
输入:
5 5 //n个顶点m条边
2 3 2 //从顶点2到顶点3的路程为2
1 2 -3
1 5 5
4 5 2
3 4 3
输出:
//顶点1到其他顶点的距离
0 -3 -1 2 4
代码:
import java.util.Scanner;
public class Bellman_Ford {
public static void main(String[] args) {
int n,m,i,check, flag;
int inf = 99999999;//假设inf = 9999999为无穷大
int[] dis = new int[10];
int[] u = new int[10]; //存储边的起始顶点
int[] v = new int[10]; //存储边的结束顶点
int[] w = new int[10]; //存储边的距离
Scanner sc = new Scanner(System.in);
n = sc.nextInt(); //顶点数
m = sc.nextInt(); //边的条数
//读入边,从点t1到t2的距离为t3
for(i=1;i<=m; i++){
//第i条边
u[i] = sc.nextInt();
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
//初始化顶点1到各个顶点的距离
for( i=1; i<=n; i++)
dis[i] = inf;
dis[1] = 0;
//Bellman-Ford算法核心语句
//只需进行n-1轮,因为在一个含有n个顶点的图中,
//任意两点间的最短路径最多包含n-1边
for(int k =1; k<=n-1; k++){
//用来标记在本轮松弛中数组dis是否发生更新
check = 0;
for(i=1; i<=m; i++){
if(dis[v[i]]> dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i];
check = 1;//数组dis发生更新,改变check的值
}
//松弛完毕后检测数组dis是否有更新
if(check == 0){
break; //如果数组dis没有更新,提前退出循环结束
}
}
//检测负权回路
//经过前面的n-1轮后还存在可以松弛的情况,肯定存在负权回路
flag = 0;
for(i=1; i<=m; i++){
if(dis[v[i]] > dis[u[i]] + w[i])
flag = 1;
}
if(flag == 1)
System.out.println("此图含有负权回路");
//打印输出
else {
for(i=1; i<=n; i++){
System.out.print(dis[i] + " ");
}
}
sc.close();
}
}
四、队列优化的Bellman-Ford
时间复杂度最坏也是O(NM)
输入:
5 7 //n个顶点m条边
1 2 2 //顶点1到顶点2的距离为2
1 5 10
2 3 3
2 5 7
3 4 4
4 5 5
5 3 6
输出:
//顶点1到其他顶点的最短路径
0 2 5 9 9
代码:
import java.util.Scanner;
/**
* 用邻接表存储图
* @author 11758
*
*/
public class Duili_Bellman_Ford {
public static void main(String[] args) {
int n,m,i,k;
int inf = 99999999;//假设inf = 9999999为无穷大
int[] dis = new int[6];
int[] book = new int[6];
int[] u = new int[8]; //存储边的起始顶点
int[] v = new int[8]; //存储边的结束顶点
int[] w = new int[8]; //存储边的距离
int[] que = new int[101];
int head = 1, tail =1;
int[] first = new int[6];
int[] next = new int[8];
Scanner sc = new Scanner(System.in);
n = sc.nextInt(); //顶点数
m = sc.nextInt(); //边的条数
//初始化顶点1到各个顶点的距离
for( i=1; i<=n; i++)
dis[i] = inf;
dis[1] = 0;
//初始化book数组,初始化为0,刚开始都不在队列中
for(i =1; i<=n; i++)
book[i] = 0;
//初始化first数组下标1-n的值为-1,表示1-n顶点暂时都没有边
for(i=1; i<=n; i++)
first[i] = -1;
//读入边
for(i=1;i<=m; i++){
//第i条边
u[i] = sc.nextInt();
v[i] = sc.nextInt();
w[i] = sc.nextInt();
//下面两句是建立邻接表的关键
next[i] = first[u[i]];
first[u[i]] = i;
}
//顶点1入队
que[tail] = 1;
tail++;
book[1] = 1;//标记顶点1已经入队
while(head<tail){//队列不为空的时候循环
k = first[que[head]];//当前需要处理的队首节点
while(k!=-1){//扫描当前顶点所有的边
if(dis[v[k]]> dis[u[k]] + w[k]){//判读是否松弛
//更新顶点1到顶点v[k]的路程
dis[v[k]] = dis[u[k]] + w[k];
//book数组用来判断顶点v[k]是否在队列中
//如果不使用一个数组来标记的话,判断一个顶点在队列中每次
//都需要丛队列的head到tail扫一遍,浪费时间
if(book[v[k]] == 0){//0表示不在队列中
//入队
que[tail] = v[k];
tail++;
//同时标记顶点v[k]已经入队
book[v[k]] = 1;
}
}
k = next[k];
}
//出队
book[que[head]] = 0;
head++;
}
for(i=1; i<=n; i++){
System.out.print(dis[i] + " ");
}
sc.close();
}
}