最短路径的常用方法一般有四种:Dijsktra算法、BF算法、SPFA算法、Floyd算法
其中,Dijsktra算法主要针对不存在负权的图,所以应用也更为广泛,下面首先介绍这个算法的两种形式
一、邻接矩阵形式
#include <bits/stdc++.h>
using namespace std;
const int MAXV=1000;
const int INF=0x3fffffff;//不要使用0x7fffffff,这样两个数相加会超出范围
int n,G[MAXV][MAXV];
int d[MAXV];
bool vis[MAXV]={false};
void Dijkstra(int s)//s为任意起点
{
//初始化距离数组
fill(d,d+MAXV,INF);
d[s]=0;//非常重要的一步,不添加函数无法“启动 ”
for(int i=0;i<n;i++)//循环n次确保所有的结点都已经加入
{
//寻找距离最短的顶点
int u=-1,MIN=INF;
for(int j=0;j<n;j++)
{
if(vis[j]==false&&d[j]<MIN)
{
u=j;
MIN=d[j];
}
}
if(u==-1) return;//说明不连通,第一次找到的肯定是s点
vis[u]=true;//加入点集,不会再改变对应的d[u]
for(int v=0;v<n;v++)//查看所有的点,看看有没有可以改变的,有就改变
{
if(vis[v]==false&&G[u][v]!=INF&&d[u]+G[u][v]<d[v])
{
d[v]=d[u]+G[u][v];
}
}
}
}
int main()
{
cout<<"ok"<<endl;
}
二、邻接表形式
#include <bits/stdc++.h>
using namespace std;
const int MAXV=1000;
const int INF=0x3fffffff;//不要使用0x7fffffff,这样两个数相加会超出范围
struct Node
{
int v,w;//分别代表顶点和边权
};
vector<Node> Adj[MAXV]; //储存邻接表
int n;
int d[MAXV];
bool vis[MAXV]={false};
void Dijkstra(int s)//s为任意起点
{
//初始化距离数组
fill(d,d+MAXV,INF);
//启动
d[s]=0;
for(int i=0;i<n;i++)//循环n次确保所有的结点都已经加入已经访问过的集合
{
//寻找距离最短的顶点(即为当前处理的顶点)
int u=-1,MIN=INF;
for(int j=0;j<n;j++)
{
if(vis[j]==false&&d[j]<MIN)
{
u=j;
MIN=d[j];
}
}
if(u==-1) return;//说明不连通,第一次找到的肯定是s点
vis[u]=true;//加入点集,不会再改变对应的d[u]
//邻接表版本只有这里开始有点不一样,但都是想要遍历所有和u相连的结点
for(int j=0;j<Adj[u].size();j++)//查看所有的点,看看有没有可以改变的,有就改变
{
int v=Adj[u][j].v;
if(vis[v]==false&&d[u]+Adj[u][v].w<d[v])
{
d[v]=d[u]+Adj[u][v].w;
}
}
}
}
有时候,题目会让我们求得具体的最短路径
直接添加一个全局变量pre[],然后每次在优化的时候就记录当前结点的前驱结点
最后使用DFS遍历,注意一定要到起点然后才开始输出(毕竟是倒序的)
void DFS(int s,int v)//起点和终点
{
if(v==s)
{
cout<<s<<endl;
return;
}
DFS(s,pre[v]);//输出v前面的路径
cout<<v<<endl;//输出v
}
另外一些时候,题目会让我们输出最优的路径一共有几条
那么我们可以添加一个全局的num[]
for(int v=0;v<n;v++)
{
if(vis[v]==false&&G[u][v]!=INF)
{
if(d[u]+G[u][v]<d[v])
{
d[v]=d[u]+G[u][v];
num[v]=num[u];
}
else if(d[u]+G[u][v]==d[v])
{
num[v]+=num[u];
}
}
}
通常题目不会太过直接,比如有时候会出现不止一条路径最短,那么接下来题目会有两种出法
(1)给边再增加一种权值(路费),需要两个新的数组cost[][]和c[]来记录,下面的例子以距离优先
只需要改优化的部分就可以了(毕竟我们还是需要遍历所有和当前结点连接起来的点)
if(vis[v]==false&&G[u][v]!=INF)
{
if(d[u]+G[u][v]<d[v])
{
d[v]=d[u]+G[u][v];
c[v]=c[u]+cost[u][v];
}
else if(d[u]+G[u][v]==d[v]&&c[u]+cost[u][v]<c[v])
{
c[v]=c[u]+cost[u][v];
}
}
给点增加权值(采购),需要两个新的数组weight[]和w[]
if(vis[v]==false&&G[u][v]!=INF)
{
if(d[u]+G[u][v]<d[v])
{
d[v]=d[u]+G[u][v];
w[v]=w[u]+weight[u][v];
}
else if(d[u]+G[u][v]==d[v]&&w[u]+weight[u][v]<w[v])
{
w[v]=w[u]+weight[u][v];
}
}
如果上面的第二权值的计算方式更加复杂的话,上面的方法就需要我们在优化的时候更加的严谨,但是仍然很容易出错,所以就有了下面的Dijkstra+DFS算法
//具体的思路就是先记录最短路径,然后根据第二判断标准选择出最好的一条路径
//第一步使用Dijkstra算法,需要记录下所有的最短路径,存在数组里面,必须要用二维vector记录:vector<int> pre[MAXV]
if(d[u]+G[u][v]<d[v])
{
d[v]=d[u]+G[u][v];
pre[v].clear();
pre[v].push_back(u);
}
else if(d[u]+G[u][v]==d[v])
{
pre[v].push_back(u);
}
//前面的寻找最小的d[u]步骤是一样的
//第二步使用DFS算法,根据第一步得到的数组求得最短路径
int optvalue;//最优值(第二标准)
vector<int> pre[MAXV];//第一步求得
vector<int> path,tempPath;
void DFS(int v)//v是当前访问结点
{
if(v==st)//st为路径的起点
{
tempPath.push_back(v);
int value=0;
//然后计算tempPath上value的值
//边权之和
for(int i=tempPath.size()-1;i>0;i--)//注意这里只要访问n-1条边
{
int id=tempPath[i],idNext=tempPath[i-1];
value+=V[id][idNext]//计算第二权值和
}
//点权之和
for(int i=tempPath.size()-1;i>=0;i++)
{
int id=tempPath[i];
value+=W[id]//计算第二点权和
}
if(value/*优于*/optvalue)
{
optvalue=value;
path=tempPath;
}
tempPath.pop_back();
return;
}
tempPath.push_back(v);
for(int i=0;i<pre[v].size();i++)
{
DFS(pre[v][i]);//递归前驱结点
}
tempPath.pop_back();//最后要保证tempPath为空
}
题目应用
1.直接给出输入和输出的样例
输入样例:
第一行:顶点数n,边数m,起点,终点
后面m行:顶点1,顶点2,距离,价格(求最短距离,最少价格)
4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20
输出样例:具体的路径+最短路径距离+最少花费
0 2 3 3 40
(1)Dijkstra算法
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXV = 510;
const int INF = 0x3fffffff;
int d[MAXV], pre[MAXV], c[MAXV];
int G[MAXV][MAXV], cost[MAXV][MAXV], n, m;
int bg, ed;
bool vis[MAXV] = { false };
void Dijkstra(int s)
{
//数组们的初始化阶段
fill(d, d + MAXV, INF);
fill(d, d + MAXV, INF);
for (int i = 0; i < n; i++)//pre数组的初始化可以将所有顶点的前驱设为自己
{
pre[i] = i;
}
//启动
d[s] = 0;
c[s] = 0;
//主要算法
for (int i = 0; i < n; i++)//需要循环n次,每次的步骤都是 第一步,先找出d[]最小不为INF且没有访问过的顶点
{
//寻找(没有访问过,d[]不是INF的)
int u = -1, MIN = INF;
for (int j = 0; j < n; j++)
{
if (vis[j] == false && d[j] < MIN)
{
u = j;
MIN = d[j];
}
}
vis[u] = true;
if (u == -1) return;//说明没有找到
//优化
for (int j = 0; j < n; j++)
{
if (G[u][j] != INF && vis[j] == false)
{
if (d[u] + G[u][j] < d[j])
{
d[j] = d[u] + G[u][j];
c[j] = c[u] + cost[u][j];
pre[j] = u;
}
else if (d[u] + G[u][j] == d[j] && c[u] + cost[u][j] < c[j])
{
c[j] = c[u] + cost[u][j];
pre[j] = u;
}
}
}
}
}
void DFS(int nowvisit)
{
if (nowvisit == bg)
{
cout << bg;
return;
}
DFS(pre[nowvisit]);
cout << " " << nowvisit;
}
int main()
{
freopen("in.txt","r",stdin);
cin>>n>>m>>bg>>ed;//输入顶点数,边数,开始结点,结束结点
int u, v;
fill(G[0], G[0] + MAXV * MAXV, INF);
fill(cost[0],cost[0]+MAXV*MAXV,INF);
for (int i=0; i<m; i++)//输入每一条边构建图
{
cin>>u>>v;
cin>>G[u][v]>>cost[u][v];
G[v][u] = G[u][v];//这里注意是有向图还是无向图,千万注意不要把v和u填反了
cost[v][u] = cost[u][v];
}
Dijkstra(bg);
DFS(ed);
cout << " " << d[ed] << " " << c[ed];
}
Dijsktra+DFS算法
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int MAXV=510;
const int INF=0x3fffffff;
int n,m,bg,ed;
int d[MAXV],minCost=INF;//显然,如果使用Dijsktra+DFS算法的话,就不需要c[]数组了
int G[MAXV][MAXV],cost[MAXV][MAXV];
bool vis[MAXV]={false};
vector<int> pre[MAXV];//不需要初始化,每次更新前就自动清零
vector<int> tempPath,path;//可以赋值,不能用普通的数组
void Dijsktra(int s)
{
fill(d,d+MAXV,INF);
d[s]=0;
for(int i=0;i<n;i++)
{
int u=-1,MIN=INF;
for(int j=0;j<n;j++)
{
if(vis[j]==false&&d[j]<MIN)
{
u=j;
MIN=d[j];
}
}
if(u==-1) return;
vis[u]=true;
for(int v=0;v<n;v++)
{
if(vis[v]==false&&G[u][v]!=INF)
{
if(d[u]+G[u][v]<d[v])
{
d[v]=d[u]+G[u][v];
pre[v].clear();
pre[v].push_back(u);
}
else if(d[u]+G[u][v]==d[v])
{
pre[v].push_back(u);
}
}
}
}
}
void DFS(int v)
{
if(v==bg)
{
tempPath.push_back(v);
int tempCost=0;//储存临时的总权值
for(int i=tempPath.size()-1;i>0;i--)
{
int id=tempPath[i],idNext=tempPath[i-1];
tempCost+=cost[id][idNext];
}
if(tempCost<minCost)
{
minCost=tempCost;
path=tempPath;
}
tempPath.pop_back();//对应进数组,就要出数组,可以保证tempPath清零,以便重复使用
return;
}
tempPath.push_back(v);
for(int i=0;i<pre[v].size();i++)//对于每一个前驱结点
{
DFS(pre[v][i]);
}
tempPath.pop_back();
}
int main()
{
freopen("in.txt","r",stdin);
fill(G[0],G[0]+MAXV*MAXV,INF);
fill(cost[0],cost[0]+MAXV*MAXV,INF);
cin>>n>>m>>bg>>ed;
int u,v;
for(int i=0;i<m;i++)
{
cin>>u>>v;
cin>>G[u][v]>>cost[u][v];
G[v][u]=G[u][v];//这里注意一下是无向图还是有向图
cost[v][u]=cost[u][v];
}
Dijsktra(bg);
DFS(ed);
int first=1;
for(int i=path.size()-1;i>=0;i--)
{
if(first)
{
cout<<path[i];
first=0;
}
else
{
cout<<" "<<path[i];
}
}
cout<<" "<<d[ed]<<" "<<minCost;
return 0;
}