最短路径算法是PAT甲级考试常考算法,具体说来,最短路径包括Dijkstra算法、Floyd算法,其余的Bellman-Ford和SPFA基本不会考(参《算法笔记》胡凡,曾磊著)
目录
一、最短路径基本概念与问题分类
1.基本概念
2.问题分类
二、Dijkstra算法
1.伪代码
2.示意图
3.参考代码
4.升级
5.Dijkstra+DFS
给定图G(V,E),求一条从起点到终点的路径,是的这条路径上经过的所有边的边权之和最小。
无负权单源最短路径:Dijkstra算法
有负权单源最短路径:Bellman-Ford算法(简称BF算法)以及BF算法的优化算法:SPFA(Shortest Path Faster Algorithm)
全源最短路径问题:Floyd算法
//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];
}
}
}
}
由于图可以用邻接矩阵和邻接表来实现,因此最短路径Dijkstra算法也有两种写法 。
3.1.邻接矩阵版
const int maxv=1000;
const int inf=1000000000;//无穷大量,也可以写作0x3fffffff(7个)
int n,G[maxv][maxv];//n为顶点数,G是邻接矩阵
int d[maxv]; //用于记录最短路径
bool vis[maxv]={false}; //用于记录是否已访问
void Dijkstra(int s){//s为源点
fill(d, d+maxv, inf);
d[s]=0;
for(int i=0; id[u]+G[u][v]){
d[v]=d[u]+G[u][v];//优化
}
}
}
}
3.2.邻接表版
struct Node{
int w;
int data;
};
vector Adj[maxn];
int n, dis[maxn];
bool vis[maxn] = {false};
void Dijkstra(int s){
fill(dis, dis+maxn, inf);
dis[s]=0;
for(int i=0; id[u]+Adj[u][v].w){
d[v]=d[u]+Adj[u][v].w;
}
}
}
}
3.3.路径求法
上面讲的方法是计算每个点到源点最小距离,没有得出源点到该点的具体路径。
下面写最短路径的求法:
一般来说,我们需要在上面的基础上增加一个数组pre[maxn],其中的每个数据pre[v]表示使v点为当前最短路径长度这样的情况下的前驱点(前面一个点,也就是例子中的u)。也就是说,在更新d[v]的时候顺便将pre[v]=u。
以邻接矩阵为例,见修改后的代码(注意看“新添加”字样):
const int maxv=1000;
const int inf=1000000000;//无穷大量,也可以写作0x3fffffff(7个)
int n,G[maxv][maxv];//n为顶点数,G是邻接矩阵
int d[maxv]; //用于记录最短路径
int pre[maxv]; //用于记录每个点的前驱点————————————————————————————(新添加)
bool vis[maxv]={false}; //用于记录是否已访问
void Dijkstra(int s){//s为源点
fill(d, d+maxv, inf);
for(int i=0; id[u]+G[u][v]){
d[v]=d[u]+G[u][v];//优化
pre[v]=u; //记录前驱点 —————————————————————————(新添加)
}
}
}
}
完成了这一步,在我们的数组pre中就记录了每个点的上一个点,上一个点也可以找到他的前一个点,直到找到源点,再将其从原点开始输出。这样一个过程其实就是递归 :
void PrintPath(int s, int v){//从源点s到结点v的最短路径
if(v==s){//当递归到源点时
cout<
至此,基本介绍了Dijkstra算法的基本使用方法,但是题目肯定不会考的这么直接,至少会给它加些包装或者升级。我们按照以往考题,总结出以下三种升级形式:
下面针对这三种情况分别对代码进行修改:
//增加边权考法
/*增加int cost[][]数组,cost[u][v]表示u->v的花费
(类似于G[][]中的权重),并增加一个数组c[],c[u]表
示从源点到u点的最小花费(类似于最短路径的数组d[])
初始化:c[s]为0,其余全为inf*/
//则,在更新最短路径片段这样表示(邻接矩阵)
for(int v=0; vd[u]+G[u][v]){
d[v]=d[u]+G[u][v];
c[v]=v[u]+cost[u][v];
}
//最短路径相同看花费较小
else if(d[v]==d[u]+G[u][v] && c[v]>v[u]+cost[u][v]){
c[v]=c[u]+cost[u][v];
}
}
}
//增加点权考法,类似于增加边权
/*weight[u]表示城市中的物资数目,w[u]表示收集到的物资最大值。
初始化时:只有w[s]为weight[s],其余初始化为0
之后再更新最短路径更新即可*/
for(int v=0; vd[u]+G[u][v]){
d[v]=d[u]+G[u][v];
w[v]=w[u]+weight[v];
}
else if(d[v]=d[u]+G[u][v] && w[v]
//直接问最短路径有几条
/*增加数组num[]表示起点到该点的最短路径图条数
初始化:只有num[s]为1,其余全为0*/
for(int v=0; vd[u]+G[u][v]){
d[v]=d[u]+G[u][v];
num[v]=num[u];
}
else if(d[v]==d[u]+G[u][v]){
num[v]=num[u]+num[v];//距离相同则累加
}
}
}
Dijkstra算法在进行多标尺或复杂情况处理还不尽完美,使用Dijkstra算法(计算最短路径)+DFS算法(按照标尺对各个路径进行比较)结合的方式可以让逻辑结构更为清晰。
要达到目的,首先需要调整pre[]数组改为vector
修改后的代码如下:
vector pre[maxn];
void Dijkstra(int s){//开始节点
//初始化
fill(d, d+maxn, inf);//最短路径
d[s]=0;
//循环n次
for(int i=0; id[u]+G[u][v]){//更新最短路径
d[v]=d[u]+G[u][v];
pre[v].clear();
pre[v].push_back(u);
}
else if(d[v]==d[u]+G[u][v]){
pre[v].push_back(u);
}
}
}
}
}
DFS代码可以修改为:
//增加变量
//path为最优路径、temp临时路径
vector path, tempPath;
int optValue;//第二指标最优值
void DFS(int v, int s){//终点和起点
//边界条件:计算第二指标,比较替换保存到最优路径
if(v==s){
tempPath.push_back(v);//把最后一个点推入路径
int value;//新建指标值
计算路径tempPath的value;
if(value优于optValue){//如果更优更新路径
optValue = value;
path = tempPath;
}
tempPath.pop_back();//删除刚加入的节点
return;
}
//递归式
tempPath.push_back(v);
for(int i=0; i
三、Floyd算法jiu
未完待续,先做题。。。