迪杰斯特拉算法,用于解决单源最短路径问题。
伪代码
//G为图,一般设成全局变量;数组d为源点到达各点的最短路径长度,s为起点
Dijstra(G, d[], s){
初始化;
for(循环n次){
u = 使d[u]最小的还未被访问的顶点的标号;
记u已被访问;
for(从u出发能到达的所有顶点v){
if(v未被访问 && 以u为中介点使s到顶点v的最短距离d[v]最优){
优化d[v];
//如果要记录路径,则
另v的前驱为u;
}
}
}
}
预定义
const int MAXV = 1000; //最大顶点数
const int INF = 1000000000; //设INF为一个很大的数
邻接矩阵版
//邻接矩阵版
int n, G[MAXV][MAXV]; //n为顶点数,MAXV为最大顶点数
int d[MAXV]; //起点到达各点的最短路径长度
bool vis[MAXV] = {false}; //标记数组,vis[i]==true表示已访问。初值均为false
void Dijkstra(int s){ //s为起点
fill(d, d+MAXV, INF); //fill函数将整个d数组赋为INF(慎用memset)
d[s] = 0; //起点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[]最小的
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];
}
}
}
}
邻接表版
//邻接表版
struct Node{
int v, dis; //v为边的目标顶点,dis为边权
};
vector<Node> Adj[MAXV]; //图G,Adj[u]存放从顶点u出发可以到达的所有顶点
int n; //n为顶点数,图G使用邻接表实现,MAXV为最大顶点数
int d[MAXV]; //起点到达各店的最短路径长度
bool vis[MAXV] = {false}; //标记数组,vis[i]==true表示已访问。初值均为false
void Dijkstra(int s){ //s为起点
fill(d, d+MAXV, INF); //fill函数将整个d数组赋为INF(慎用memset)
d[s] = 0; //起点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++){
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]
}
}
}
如果需要记录一条完整的路径,那么可以在更新d[v]的时候记录前驱。
DFS函数用来递归求路径。
邻接矩阵版:
//还可以利用pre[]数组,表示从起点s到顶点v的最短路径上v的前一个顶点(即前驱结点)的编号
//邻接矩阵版
int n, G[MAXV][MAXV]; //n为顶点数,MAXV为最大顶点数
int d[MAXV]; //起点到达各点的最短路径长度
int pre[MAXV]; //pre[v]表示从起点到顶点v的最短路径上v的前一个顶点(新添加)
bool vis[MAXV] = {false}; //标记数组,vis[i]==true表示已访问。初值均为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; //起点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[]最小的
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];
pre[v] = u; //记录v的前驱顶点是u(新添加)
}
}
}
}
//求路径
void DFS(int s, int v){ //s为起点编号,v为当前访问的顶点编号(从终点开始递归)
if(v == s){ //如果当前已经到达起点s,则输出起点并返回
printf("%d\n",s);
return;
}
DFS(s, pre[v]); //递归访问v的前驱结点pre[v]
printf("%d\n",v); //从最深处return回来之后,输出每一层的顶点号
}
如果碰到有两条及以上可以达到最短距离的路径,题目可能给出一个第二标尺(第一标尺是距离),要求在所有最短路径中选择第二标尺最优的一条路径。而第二标尺常见的是以下三种出题方法或其组合:
- 每条边再增加一个边权(比如花费),要求多条路径取花费最小(边权也可以是其他含义,要求其边权和最大)
- 每个点增加一个点权(比如收集到的物资),要求多条路径取点权之和最大(如果点权是其他含义的话也可以是最小)
- 直接问有多少条最短路径
解题方法:只需要增加一个数组来存放新增的边权 或 点权 或 最短路径条数,然后在Dijkstra算法中修改优化d[v]的那个步骤即可。
详细解法:
- 新增边权。以新增的边权代表花费为例。
- cost[u][v]表示u->v的花费(由题目输入)
- 增加数组c[],表示从起点s到达顶点u的最小花费为c[u]。初始化时c[s]=0,其余c[u]=INF
- 在d[u]+G[u][v]<d[v]时更新d[v]和c[v],而当d[u]+G[u][v]==d[v]且c[u]+cost[u][v]<c[v]时更新c[v]
for(int v=0; v<n; v++){
//如果v未访问 && u能到达v
if(vis[v] == false && G[u][v] != INF){ //以u为中介点可以使d[v]最优
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]; //最短距离相同时看能否使c[v]最优
}
}
}
- 新增点权。以新增的点权代表城市能收集到的物资为例。
- weight[u]表示城市u中的物资数目(由题目输入)
- 增加数组w[],表示从起点s到达顶点u可以收集到的最大物资为w[u]。初始化时w[s]为weight[s],其余w[u]=0
- 在d[u]+G[u][v]<d[v]时更新d[v]和c[v],而当d[u]+G[u][v]==d[v]且w[u]+weight[v]>w[v]时更新w[v]
for(int v=0; v<n; v++){
//如果v未访问 && u能到达v
if(vis[v] == false && G[u][v] != INF){
if(d[u] + G[u][v] < d[v]){ //以u为中介点可以使d[v]更优
d[v] = d[u] + G[u][v];
w[v] = w[u] + weight[v];
}else if(d[u] + G[u][v] == d[v] && w[u] + weight[v] > w[v]){
w[v] = w[u] + weight[v]; //最短路径相同时看能否使w[v]更优
}
}
}
- 求最短路径条数。
- 增加数组num[],表示从起点s到达u的最短路径条数为num[u],初始化时num[s]=1,其余num[u]=0
- 在d[u]+G[u][v]<d[v]时更新d[v],并让num[v]继承num[u],而当d[u]+G[u][v]==d[v]时将num[u]加到num[v]上
for(int v=0; v<n; v++){
//如果v未访问 && u能到达v
if(vis[v] == false && G[u][v] != INF){
if(d[u] + G[u][v] < d[v]){ //以u为中介点可以使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]; //最短距离相同时同时累加num
}
}
}
参考文献
- 《算法笔记》