最短路径是图论中经典的问题:给定图G(V,E)和起点S,终点T,求一条从起点到终点的路径,使得这条路径上经过的所有边的边权之和最小。
Dijkstra算法可以用来解决单源最短路径问题,即给定图G和顶点S,得到S到达其他每个顶点的最短路径。基本思想是:对图G(V,E)设置一个集合S,用来存放已经访问过的顶点,然后每次从集合V-S(没有被访问的顶点)中选择与起点s最短距离最小的一个顶点u,访问并加入集合S。接着以u为中介点,优化起点s与u能达到的所有顶点v之间的最短距离。上述操作重复n次(n为顶点个数),直到集合S中包含了所有顶点。
具体实现:
1.集合S用一个bool型的数组vis[]实现,vis[i]=true表示顶点Vi被访问,=false未被访问。
2.定义int类型数组dis[]表示起点s到顶点Vi的最短距离。初始时将dis[s]赋值为0,其余顶点都赋为一个很大的数(1000000000或0x3fffffff)。
Dijkstra算法有邻接表和邻接矩阵两种写法,区别在于邻接矩阵需要枚举所有顶点查看顶点v是否可以到达u,而邻接表可以直接得到u能达到的顶点v。
(1)邻接矩阵版
const int MAXV=1000;
const int INF=1000000000;
int n;
int G[MAXV][MAXV];
int dis[MAXV];
bool vis[MAXV]={false}
void Dijkstra(int s){
fill(dis,dis+MAXV,INF); //将整个dis数组赋值为INF
dis[s]=0;
for(int i=0;i<n;i++){
int u=-1;
int min=INF;
for(int j=0;j<n;j++){
if(vis[j]==false && dis[j]<min){
min=dis[j];
u=j;
}
}
if(u==-1)
return; //找不到dis[u]<INF的顶点u,说明其他顶点与起点s都不连通
vis[u]=true;
for(int v=0;v<n;v++){
if(vis[v]==false && G[u][v]!=INF && dis[v]>dis[u]+G[u][v])
dis[v]=dis[u]+G[u][v];
}
}
}
(2)邻接表版
struct Node{
int v; //边的目标定点
int dis; //边权
}
const int MAXV=1000;
const int INF=1000000000;
int n;
vector<Node> Adj[MAXV];
int dis[MAXV];
bool vis[MAXV]={false};
void Dijkstra(int s){
fill(dis,dis+MAXV,INF);
dis[s]=0;
for(int i=0;i<n;i++){
int u=-1;
int min=INF;
for(int j=0;j<n;j++){
if(dis[j]<min){
min=dis[j];
u=j;
}
}
if(u==-1)
return;
vis[u]=true;
for(int j=0;j<Adj[u].size();j++){
int v=Adj[u][j].v;
if(vis[v]==false && dis[v]>dis[u]+Adj[u][j].v){
dis[v]=dis[u]+Adj[u][j].v;
}
}
}
}
上述代码只求解了最短距离,想要得到最短路径,可以设置一个数组pre[],pre[v]表示从起点到顶点v的最短路径上v的前一个顶点的编号,只需要在优化起点s与u能达到的所有顶点v之间的最短距离那里增加一行:
int pre[MAXV];
if(vis[v]==false && G[u][v]!=INF && dis[v]>dis[u]+G[u][v]){
dis[v]=dis[u]+G[u][v];
pre[v]=u;
}
得到了每个顶点的前驱,就可以递归地不断利用pre[]寻找前驱直到到达起点。
void DFS(int s,int v){ //s为起点,v为当前访问的顶点
if(v==s){
printf("%d\n",s);
return;
}
DFS(s,pre[v]);
printf("%d\n",v);
}
以上是Dijkstra算法的基本用法,在实际算法考试中可能会出现从起点到终点的最短路径不止一条,因此题目会给出第二标尺,要求在所有最短路径中选择第二标尺最优的路径,最常见的有以下几种:
1.给每条边再增加一个边权(花费),最短路径有多条时要求花费之和最小。
2.给每个顶点增加一个点权,最短路径有多条时要求点权之和最大。
3.问有多少条最短路径
这三种情况都只需要新增一个数组来存放新增的点权或边权或路径条数,修改优化dis[v]的步骤即可。
1.新增边权。cost[u][v]表示顶点u到v的花费(题目输入),新增一个数组c[],记录顶点到其他顶点的最少花费。初始时c[s]=0,其余顶点为INF。
int cost[MAXV][MAXV];
int c[MAXV];
for(int v=0;v<n;v++){
if(vis[v]==false && G[u][v]!=INF){
if(dis[v]>dis[u]+G[u][v]){
dis[v]=dis[u]+G[u][v];
c[v]=c[u]+cost[u][v];
}
if(dis[v]==dis[u]+G[u][v] && c[v]>c[u]+cost[u][v]){
c[v]=c[u]+cost[u][v]; //最短路径相同时优化c[v]
}
}
}
2.新增点权。weight[u]表示顶点u的点权,在设置一个数组w[]表示顶点到其他顶点的最大点权。初始时w[s]=weight[s],其余顶点为0。
int weight[MAXV];
int w[MAXV];
for(int v=0;v<n;v++){
if(vis[v]==false && G[u][v]!=INF){
if(dis[v]>dis[u]+G[u][v]){
dis[v]=dis[u]+G[u][v];
w[v]=w[u]+weight[v];
}
else if(dis[v]==dis[u]+G[u][v] && w[v]<w[u]+weight[v]){
w[v]=w[u]+weight[v]; //最短距离相同时优化w[v]
}
}
}
3.最短路径条数。增加一个数组num[],表示从顶点到其他顶点的最短路径条数,初始时num[s]=1,其余为0。
int num[MAXV];
for(int v=0;v<n;v++){
if(vis[v]==false && G[u][v]!=INF){
if(dis[v]>dis[u]+G[u][v]){
dis[v]=dis[u]+G[u][v];
num[v]=num[u];
}
else if(dis[v]==dis[u]+G[u][v])
num[v]+=num[u];
}
}
实例: PAT 1003 Emergency
As an emergency rescue team leader of a city, you are given a special map of your country. The map shows several scattered cities connected by some roads. Amount of rescue teams in each city and the length of each road between any pair of cities are marked on the map. When there is an emergency call to you from some other city, your job is to lead your men to the place as quickly as possible, and at the mean time, call up as many hands on the way as possible.
Input Specification:
Each input file contains one test case. For each test case, the first line contains 4 positive integers: N (≤500) - the number of cities (and the cities are numbered from 0 to N−1), M - the number of roads, C1and C2 - the cities that you are currently in and that you must save, respectively. The next line contains N integers, where the i-th integer is the number of rescue teams in the i-th city. Then M lines follow, each describes a road with three integers c1 , c2 and L, which are the pair of cities connected by a road and the length of that road, respectively. It is guaranteed that there exists at least one path from C1 to C2 .
Output Specification:
For each test case, print in one line two numbers: the number of different shortest paths between C1 and C2 , and the maximum amount of rescue teams you can possibly gather. All the numbers in a line must be separated by exactly one space, and there is no extra space allowed at the end of a line.
Sample Input:
5 6 0 2
1 2 1 5 3
0 1 1
0 2 2
0 3 1
1 2 1
2 4 1
3 4 1
Sample Output:
2 4
题意:给出每个城市的点权即城市之间的距离,要求计算出指定起点到终点城市的最短路径的条数和最大点权和。
思路:常规的Dijkstra算法加上点权和路径条数即可(注意:两个城市之间的道路属于无向边)。
AC代码:
#include <stdio.h>
#include <algorithm>
using namespace std;
const int MAXV=510;
const int INF=1000000000;
int G[MAXV][MAXV];
int N;
int dis[MAXV];
bool vis[MAXV]={false};
int w[MAXV];
int weight[MAXV];
int num[MAXV];
void Dijkstra(int s){
fill(dis,dis+MAXV,INF);
dis[s]=0;
fill(w,w+MAXV,0);
w[s]=weight[s];
fill(num,num+MAXV,0);
num[s]=1;
for(int i=0;i<N;i++){
int u=-1;
int Min=INF;
for(int j=0;j<N;j++){
if(vis[j]==false && dis[j]<Min){
Min=dis[j];
u=j;
}
}
if(u==-1)
return;
vis[u]=true;
for(int v=0;v<N;v++){
if(vis[v]==false && G[u][v]!=INF){
if(dis[v]>dis[u]+G[u][v]){
dis[v]=dis[u]+G[u][v];
w[v]=w[u]+weight[v];
num[v]=num[u];
}
else if(dis[v]==dis[u]+G[u][v]){
if(w[v]<w[u]+weight[v])
w[v]=w[u]+weight[v];
num[v]+=num[u];
}
}
}
}
}
int main(){
int M,C1,C2;
int c1,c2,L;
scanf("%d%d%d%d",&N,&M,&C1,&C2);
for(int i=0;i<N;i++)
scanf("%d",&weight[i]);
fill(G[0],G[0]+MAXV*MAXV,INF);
for(int i=0;i<M;i++){
scanf("%d%d%d",&c1,&c2,&L);
G[c1][c2]=G[c2][c1]=L;
}
Dijkstra(C1);
printf("%d %d\n",num[C2],w[C2]);
return 0;
}