图算法专题(二)【图论最短路径问题】

定义

  给定图G<V,E>,求一条从起点到终点的路径,使得路径上经过的所有的边权值之和最小。称为最短路径问题。
  常用算法有Dijkstra算法、Bellman-Ford算法、SPFA算法和Floyd算法。

Dijkstra算法

  迪杰斯特拉算法用来解决单源最短路问题(即给定图G和起点S,求S到其他每个顶点的最短距离)。同时无法处理负权

策略:

  设置集合S存放已被访问的顶点,然后执行n次下面的两个步骤(n为顶点个数):
  ① 每次从集合V-S中选择与起点s的最短距离最小的一个顶点(记为u),访问并加入集合S
  ② 之后,令u为中介点,更新起点s与所有能经过u到达的顶点v之间的最短距离。

具体实现:

  考虑集合S的实现,起点s到达顶点Vi的最短距离的实现

  1. 集合S使用bool型数组vis[]来实现,当vis[i] == true时,表示Vi已被访问;vis[i] == false时,表示Vi未被访问。
  2. 令int型数组d[ ]表示起点s到顶点Vi的最短距离,初始时除了起点s的d[s] = 0,其他的d[i] 是一个很大的数,经常使= 0x3fffffff 来表示inf

Dijkstra伪代码:

//设G为图,一般设成全局变量;数组d为源点到达各点的最短路径长度,s为起点
Dijkstra (G,d[], s){
   
	初始化;
	for(循环n次){
   
		u = 使d[u]最小的还未被访问的顶点的标号;
		记u已被访问;
		for(从u出发能够到达所有顶点v){
   
			if(v未被访问 && 以u中转使s到v的最短距离d[v]更小){
   
				更新优化d[v];
			}
		}
	}
}

具体实现代码

  此时图可以使用邻接矩阵或者邻接表两种形式存储。当顶点数<1000时,使用邻接矩阵(也较为简单)。

邻接矩阵表示:

枚举所有顶点来查看v是否可由u到达

C++:
#include<bits/stdc++.h>
using namespace std;
const int MAXV = 1000;//最大顶点数
const int INF = 0x3fffffff;//设INF为一个很大的数  

int n,G[MAXV][MAXV]; //n为顶点数 
int d[MAXV]; //起点到达各个顶点的最短路径长度 
bool vis[MAXV] = {
   false}; //标记是否访问的数组
 
void Dijkstra(int s){
    //s为起点 
	fill(d,d+MAXV,INF);//fill函数将整个d数组赋值为INF(慎用memset)
	d[s] = 0;
	for(int i=0;i<n;i++){
    //循环n次 
		int u =-1 , MIN = INF; //u使d[u]最小,MIN存放该最小的d[u]
		for(int j =0;j<n;j++){
    //找到未被访问的顶点中d[u]最小的 
			if(vis[j]==false && d[j]<MIN){
   
				u = j;
				MIN = d[j];
			}	
		} 
		//找不到小于INF的d[u],说明剩下的顶点和起点s不连通
		if(u == -1) return; //找不到更小的顶点
		vis[u] = true; //标记u为已访问 
		for(int v = 0;v<n;v++){
    
			//如果v未访问 && u能到达v && 以u为中转可以优化d[v]距离
			if(vis[v]== false && G[u][v] != INF && d[u]+G[u][v] < d[v]){
   
				d[v] = d[u]+G[u][v];//优化d[v] 
			} 
		}	
	}	 
} 
邻接表表示:

可以直接得到u能够到达的顶点v,无须遍历所有顶点,当顶点较多的时候可以使用

#include<bits/stdc++.h>
using namespace std;
const int MAXV = 1000;//最大顶点数
const int INF = 0x3ffffff;//设INF为一个很大的数 
struct Node{
   
	int v,dis; //v为边的目标顶点,dis为边权值 
};
vector<Node> Adj[MAXV];//存放图G,Adj[u]存放从顶点u出发的所有可达顶点
int n;//顶点数
int d[MAXV]; //起点到达各个顶点的最短路径长度 
bool vis[MAXV] = {
   false}; //标记是否访问的数组

void Dijkstra(int s){
    //s为起点 
	fill(d,d+MAXV,INF);//fill函数将整个d数组赋值为INF(慎用memset)
	d[s] = 0;
	for(int i=0;i<n;i++){
    //循环n次 
		int u =-1 , MIN = INF; //u使d[u]最小,MIN存放该最小的d[u]
		for(int j =0;j<n;j++){
    //找到未被访问的顶点中d[u]最小的 
			if(vis[j]==false && d[j]<MIN){
   
				u = j;
				MIN = d[j];
			}	
		} 
		//找不到小于INF的d[u],说明剩下的顶点和起点s不连通
		if(u == -1) return; //找不到更小的顶点
		vis[u] = true; //标记u为已访问 
		//只有下面这个for循环与邻接矩阵不同
		for(int j = 0;j<Adj[u].size();j++){
   
			int v = Adj[u][j].v;//通过邻接表直接获得u能到达的顶点v
			if(vis[v] == false && d[u]+Adj[u][j].dis <d[v]){
   
				//如果v未访问&&以u为中转可以优化d[v]距离
				d[v] = d[u]+Adj[u][j].dis;//优化d[v] 
			}
		} 
	}
}

  如果题目给出的无向边而不是有向边,这时只需要把无向边当成两条指向相反的有向边即可。对于邻接矩阵来说,一条u到v之间的无向边,在输入时可以分别对G[u][v] = d[v][u] = value ; 对于邻接表来说,只需要在u的邻接表Adj[u]的末尾添加上v,并在v的邻接表Adj[v]的末尾加上u即可。

求最短路径本身

设置一个数组pre[ ],pre[v]表示从起点s到顶点v的最短路径上v的前一个顶点的编号。这样,当伪代码中的条件成立时,就可以将u赋值给pre[v],最终就能把最短路径上每一个顶点的前驱顶点记录下来。

伪代码
for(从u出发能够到达所有顶点v){
   
	if(v未被访问 && 以u中转使s到v的最短距离d[v]更小){
   
		更新优化d[v];
		令v的前驱为u;
	}
}
邻接矩阵表示:
C++:
#include<bits/stdc++.h>
using namespace std;
const int MAXV = 1000;//最大顶点数
const int INF = 0x3fffffff;//设INF为一个很大的数  

int n,G[MAXV][MAXV]; //n为顶点数 
int d[MAXV]; //起点到达各个顶点的最短路径长度 
int pre[MAXV]; //pre[v]表示从起点到顶点v的最短路径上v的前一个顶点【新添加】
bool vis[MAXV] = {
   false}; //标记是否访问的数组
 
void Dijkstra(int s){
    //s为起点 
	fill(d,d+MAXV,INF);//fill函数将整个d数组赋值为INF(慎用memset)
	for(int i =0;i<n;i++) pre[i] = i; //初始状态设每个点的前驱为自身【新添加】
	d[s] = 0;
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值