算法之最短路径

一、求任意两点之间的最短路径——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();
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值