【算法->图】Dijkstra迪杰斯特拉算法 C++实现

迪杰斯特拉算法,用于解决单源最短路径问题。

伪代码

//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回来之后,输出每一层的顶点号
}

如果碰到有两条及以上可以达到最短距离的路径,题目可能给出一个第二标尺(第一标尺是距离),要求在所有最短路径中选择第二标尺最优的一条路径。而第二标尺常见的是以下三种出题方法或其组合:

  1. 每条边再增加一个边权(比如花费),要求多条路径取花费最小(边权也可以是其他含义,要求其边权和最大)
  2. 每个点增加一个点权(比如收集到的物资),要求多条路径取点权之和最大(如果点权是其他含义的话也可以是最小)
  3. 直接问有多少条最短路径

解题方法:只需要增加一个数组来存放新增的边权 或 点权 或 最短路径条数,然后在Dijkstra算法中修改优化d[v]的那个步骤即可。

详细解法:

  1. 新增边权。以新增的边权代表花费为例。
    • 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]最优
        }
    }
}
  1. 新增点权。以新增的点权代表城市能收集到的物资为例。
    • 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]更优
        }
    }
}
  1. 求最短路径条数。
    • 增加数组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
        }
    }
}

参考文献

  1. 《算法笔记》
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值