数据结构——最短路径问题


前言

两个顶点之间的最短路径问题就是求一条路径可以令两顶点沿途各边权值之和最小


一、问题分类

对于这个问题,可以分为两种情况:
1.单源最短路径:从固定起点出发,求最短路径;
2.多源最短路径:求任意两顶点间最短路径。
除此以外,在每种情况内部有权图跟无权图有向图跟无向图
有权图是无权图的一般形式,而有向图也是无向图的一般形式,所以在讨论中我们只会简单提及特殊形式,而更看重有权有向图的普适性。


二、单源最短路径

1.无权图(BFS)

(1)问题分析

虽为无权,其实可以视作各边的权重相同,那么问题就转换成了我们熟悉的BFS问题,两点间的最短路径也就名副其实了,BFS可以很方便的求出最短路径。
复杂度也容易得知,为O(|V|+|E|),意味着每条边跟每个节点最多访问一次。

(2) 路径记录

我们最终的目标还是需要求出这个最短路径的具体内容,于是乎我们需要通过一个特殊的数组来回溯路径,每个元素的下标代表着这个节点的标号,而元素中的数据就是这个节点在路径中的上一个节点
例如:若起点为节点1,终点为节点8,此时有 path[8]=6,path[6]=4,path[4]=1,即意味着经过上述操作后,通过给定的节点8便可回溯得知路径为:8->6->4->1。

2.有权图(朴素DiskStra算法)

(1)问题分析

在讨论问题之前我们先要确定这个问题的前提:各边权值非负。
在这里插入图片描述
在右图中,存在一个边的权值为负,且每经过这个“负值圈”,都可令权值和变小,那么在这种情况下则不存在最短路径。

(2)算法介绍

思路有点像动态规划跟BFS的结合,由已知推未知,步步求优,又是一个层次向下一个层次的不断推广

(3)代码实现

#include<bits/stdc++.h>
using namespace std; 
#define MaxNum 50
#define MaxInt 32767
#define MaxEdgeNum 50
//邻接矩阵
typedef int VertexType;
typedef int EdgeType;
struct Graph{
	VertexType vexs[MaxNum];//顶点表 
	EdgeType arcs[MaxNum][MaxNum];//邻接矩阵表 
	int vexnum,edgenum;//顶点数,边数 
}; 
void createGraph(Graph *H){
	printf("请输入顶点数:");
	cin>>H->vexnum;
	printf("\n请输入边数:");
	cin>>H->edgenum;
	
	//初始化顶点表 
	for(int i=0;i<H->vexnum;i++){
		H->vexs[i]=i; 
	} 
	for(int i=0;i<H->vexnum;i++){
		for(int j=0;j<H->vexnum;j++){
			H->arcs[i][j]=MaxInt;
			if(i==j) H->arcs[i][j]=0;
		}
	}
	printf("请输入边的信息:\n");
	for(int i=0;i<H->edgenum;i++){
		int x,y,w;
		scanf("%d%d%d",&x,&y,&w);
		H->arcs[x][y]=w;
	}
}
//------------------------------------------------------------------
void Dijkstra(Graph *H,int dist[],int path[],int v){//此时v为起点 
	int n=H->vexnum;
	int set[n];//set数组用于记录该顶点是否被标记,即是否经过 
	//1.初始化起点,并更新其周围的邻接点 
	for(int i=0;i<n;i++){
		set[i]=0;
		dist[i]=H->arcs[v][i];//有向图,只需要在邻接矩阵里面v的那一行中检索即可 
		(dist[i]<MaxInt)?path[i]=v:path[i]=-1;
					//当小于即可更新数据,说明两点相邻 
	}
	set[v]=1;		//标记,表示这个点已经被经过 
	path[v]=-1;	//起点不存在“父节点 ”,需要从值v更正为-1 
	
	//2.处理完起点之后,处理其余顶点 
for(int i=1;i<n;i++){//共n-1个顶点 
	int min=MaxInt;
		
	//第一步:从剩余的顶点中选出一个dist最小的顶点 
		
		//全扫描,适用于稠密图,边的个数远多于顶点个数,时间复杂度为 O(|V|^2+|E|) 
	for(int j=0;j<n;j++){
		if(set[j]==0&&dist[j]<min){
			v=j;
			min=dist[j];
		}
	}
		/*  最小堆,适用于稀疏图,边的个数与顶点数相差不大,时间复杂度为O(|E|*log|V|)
	 	将会专门用一篇博客来讲*/ 
	
	set[v]=1;	
	//第二步:在将新结点并入后,起点到各顶点的最短距离有可能将会发生变化
		  /*对顶点V的未被标记过的邻接点的dist值进行更新,设该点为W,W可能会被多次更新 
		下面没有直接区分邻接点,但非邻接点的边已经被初始化为了MaxInt*/ 
	for(int j=0;j<n;j++){
		if(set[j]==0&&dist[v]+H->arcs[v][j]<dist[j]){
		dist[j]=dist[v]+H->arcs[v][j];
		path[j]=v;
		}
	} 	
}
}
//------------------------------------------------------------------
void print(Graph g,int dist[],int path[]){
//这里仅仅是以表格形式输出结果,如果要实现仅输入终点便可输出路径的功能可以使用递归 
	int n=g.vexnum;
	printf("       ");
	for(int i=0;i<n;i++) printf("%d  ",i);
  
	printf("\ndist[]:");
	for(int i=0;i<n;i++) printf("%d  ",dist[i]);
 
	printf("\npath[]:");
	for(int i=0;i<n;i++) printf("%d  ",path[i]);
}
int main(){
	Graph g;
	createGraph(&g);
	int dist[g.vexnum];//用来记录“局部 ”最优解,到起点的最短路径和 
	int path[g.vexnum];//用来回溯路径 
	Dijkstra(&g,dist,path,0);
	print(g,dist,path);//以表格形式输出 
} 

朴素Dijkstra模板
朴素Dijkstra例题

#include<iostream>
#include<cstring>
using namespace std;
const int N = 505;
int g[N][N];
bool st[N];
int dis[N];
int n,m;
int d(){
	memset(dis,0x3f,sizeof dis);
	int t=-1;
	dis[1]=0;
	for(int i=0;i<n;i++){
		for(int j=1;j<=n;j++)
			if(!st[j]&&(t==-1||dis[t]>dis[j]))
				t=j;
		st[t]=1;
		for(int j=1;j<=n;j++){
			if(!st[j])
				dis[j]=min(dis[j],dis[t]+g[t][j]);
		}
	}
	if(dis[n]==0x3f3f3f)return -1;
	else return dis[n];
}
int main(){
	cin>>n>>m;
	memset(g,0x3f,sizeof g);
	for(int i=0;i<m;i++){
		int x,y,z;
		cin>>x>>y>>z;
		if(x==y)continue;
		g[x][y]=min(g[x][y],z);
	}
	cout<<d()<<endl;
}

(4)思考

对于朴素Dijkstra算法来说时间复杂度为O( n 2 n^2 n2),只与节点数有关,所以适用于稠密图,也就是边数比较多的图中,而我们通常用邻接矩阵来存储稠密图。


三、多源最短路径

1.问题分析

对于多源问题,可以将其视为单源的更一般化的形式,那么我们的问题就转化为了如何将不同的单源最短路径结合在一起

2.枚举

(1)思路

在循环中重复调用单源算法,那么我们已知原来的时间复杂度为O(|V|^2+|E|),而此时需要重复调用|V|次,于是枚举思路的时间复杂度为:
O(|V|^3+|E|*|V|)
这种思路适用于稀疏图。

3.Floyd算法

(1)思路分析

对于顶点 i,j,满足i不等于j的前提下,求dist[i][j],我们可以引入另一个顶点k,将i到j的过程划分为i->k->j(k可以与i或j的值相同,dist[i][i]=0,即便i,j相邻也无影响)。
由此可知,存在式子:
dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j])
这个式子让我们联想到了动态规划,对于变量i,j,k只需要使用三重循环,便可以保证能够自左向右,自上而下**的遍历每种情况,并不断完善dist的结果

(2)代码实现

#include<bits/stdc++.h>
using namespace std; 
#define MaxNum 50
#define MaxInt 32767
#define MaxVexNum 50
//邻接矩阵
typedef int VertexType;
typedef int EdgeType;
struct Graph{
	VertexType vexs[MaxNum];//顶点表 
	EdgeType arcs[MaxNum][MaxNum];//邻接矩阵表 
	int vexnum,edgenum;//顶点数,边数 
}; 
void createGraph(Graph *H){
	printf("请输入顶点数:");
	cin>>H->vexnum;
	printf("\n请输入边数:");
	cin>>H->edgenum;
	
	//初始化顶点表 
	for(int i=0;i<H->vexnum;i++){
		H->vexs[i]=i; 
	} 
	for(int i=0;i<H->vexnum;i++){
		for(int j=0;j<H->vexnum;j++){
			H->arcs[i][j]=MaxInt;
			if(i==j) H->arcs[i][j]=0;
		}
	}
	printf("请输入边的信息:\n");
	for(int i=0;i<H->edgenum;i++){
		int x,y,w;
		scanf("%d%d%d",&x,&y,&w);
		H->arcs[x][y]=w;
	}
}
//------------------------------------------------------------------
void Floyd(Graph g,int path[][MaxVexNum]){
	int n=g.vexnum;
	int dist[n][n];
	//第一步:初始化path[][]和dist[][]数组 
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			dist[i][j]=g.arcs[i][j];
			path[i][j]=-1; 
		}
	}
	//第二步:三重循环,寻找最短路径 
	for(int k=0;k<n;k++){//第一层是代表中间结点 
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				if(dist[i][j]>dist[i][k]+dist[k][j]){
					dist[i][j]=dist[i][k]+dist[k][j];
					path[i][j]=k;
				}
			}
		} 
	} 
}
//------------------------------------------------------------------
void PrintGraph(Graph g){//以列表形式输出各顶点之间的最短路径 
	printf("邻接矩阵为:\n");
	for(int i=0;i<g.vexnum;i++) {
		printf("  %d",g.vexs[i]);
	}
	printf("\n");
	for(int i=0;i<g.vexnum;i++){
		printf("%d ",g.vexs[i]);
		for(int j=0;j<g.vexnum;j++){
			if(g.arcs[i][j]==32767){
				printf("∞ "); 
			}
			else{
				printf("%d  ",g.arcs[i][j]);
			}	
		}
		printf("\n");
	} 
}
//------------------------------------------------------------------
int main(){
	Graph g;
	createGraph(&g);
	PrintGraph(g);
	int path[MaxVexNum][MaxVexNum];
	Floyd(g,path);
	//同样可以用递归回溯出任意两点间的最短路径 
} 
  • 1
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Dijkstra算法可以用来求带权有向图上的最短路径,下面是Java实现: ```java import java.util.*; public class DijkstraAlgorithm { private static final int MAX = Integer.MAX_VALUE; // 定义无穷大 public static void dijkstra(int[][] graph, int start) { int n = graph.length; // 图的大小 int[] dist = new int[n]; // 存储起点到各个点的最短距离 boolean[] visited = new boolean[n]; // 标记各个结点是否已经访问过 int[] prev = new int[n]; // 存储到达各个结点的前驱结点 // 初始化 for (int i = 0; i < n; i++) { dist[i] = MAX; visited[i] = false; prev[i] = -1; } dist[start] = 0; // 循环n-1次,每次确定一个顶点的最短路径 for (int i = 0; i < n - 1; i++) { int minDist = MAX; int u = -1; // 找到当前未访问的结点中距离起点最近的结点 for (int j = 0; j < n; j++) { if (!visited[j] && dist[j] < minDist) { minDist = dist[j]; u = j; } } if (u == -1) { break; } visited[u] = true; // 更新与u相邻的结点的最短距离 for (int j = 0; j < n; j++) { if (!visited[j] && graph[u][j] != MAX) { int newDist = dist[u] + graph[u][j]; if (newDist < dist[j]) { dist[j] = newDist; prev[j] = u; } } } } // 输出结果 System.out.println("起点为" + start + "的最短路径如下:"); for (int i = 0; i < n; i++) { if (i != start && dist[i] != MAX) { System.out.print("从" + start + "到" + i + "的最短路径为:" + start); int j = i; while (j != start) { System.out.print(" -> " + j); j = prev[j]; } System.out.println(",路径长度为:" + dist[i]); } } } public static void main(String[] args) { int[][] graph = { {0, 1, 12, MAX, MAX}, {MAX, 0, 9, 3, MAX}, {MAX, MAX, 0, MAX, 5}, {MAX, MAX, 4, 0, 13}, {MAX, MAX, MAX, MAX, 0} }; dijkstra(graph, 0); } } ``` 运行结果: ``` 起点为0的最短路径如下: 从0到1的最短路径为:0 -> 1,路径长度为:1 从0到2的最短路径为:0 -> 1 -> 3 -> 2,路径长度为:12 从0到3的最短路径为:0 -> 1 -> 3,路径长度为:4 从0到4的最短路径为:0 -> 1 -> 3 -> 2 -> 4,路径长度为:17 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值