最短路算法Dijkstra

1. Dijkstra算法

只能计算权重为正的情况,如有重边取最小值作为边,输出路径用dfs。

# include<iostream>
# include<cstring>
using namespace std;
int n,m;
const int N=510,M=100010;
int g[N][N],used[N],d[N],pre[N];


int Dijkstra(){
    d[1]=0;
    //找v-s中距离最小的点加入s,进行n-1次操作
    for(int i=0;i<n;i++){
        int t=-1;
        //找最小的点
        for(int j=1;j<=n;j++){
            if(!used[j]&&(t==-1||d[t]>d[j])){
                t=j;
            }
        }
        //加入s
        used[t]=1;
        //更新距离
        for(int j=1;j<=n;j++){
            if(!used[j]&&d[t]+g[t][j]<d[j]) 
            {
                d[j]=d[t]+g[t][j];
                //记录前驱节点
                pre[j]=t;
            }
            
        }
    }
    if(d[n]==0x3f3f3f3f) return -1; //注意是4个3f
    else return d[n];
}

//输出路径
void dfs(int s,int d){
    // 如果已经到达起点,则返回起点在返回
    if(s==d){
        printf("%d ",s);
        return;
    }
    dfs(s,pre[d]);
    printf("%d ",d);//从最深处回来之后,输出每一层的顶点号
}

int main(){
    // ios::sync_with_stdio(false);
    // cin.tie(0);
    cin>>n>>m;
    memset(d,0x3f,sizeof d);
    memset(g,0x3f,sizeof g);
    while(m--){
        int a,b,c;
        
        cin>>a>>b>>c;
        g[a][b]=min(g[a][b],c); //处理重边
    }
    printf("%d\n",Dijkstra());
    //输出路径
    cout<<"---------"<<endl;
    dfs(1,n);
    return 0;
}

Dijkstra算法的变种

如果有多条最短路,可能会出现如下的变种:

  1. 给每条边加上花费,要求最短路并且花费最少;
  2. 给每个点加上报酬,要求经过的点报酬最多;
  3. 直接问有几条最短路。

对于第一种情况:设置数组cost[u][v]表示u->v的花费,令从起点s到终点u的最小花费为 c[u],只需改变下面几行代码:

for(int j=1;j<=n;j++){
	if(!used[j]) 
	{
	if(d[j]>d[t]+g[t][j]){//距离不相同,说明路径唯一
        c[j]=cost[t][j]+c[t];
        d[j]=d[t]+g[t][j];
    }else if(d[j]==d[t]+g[t][j]&&c[j]>cost[t][j]+c[t]) 
        {//距离相同,说明路径不唯一,更新最小花费
            c[j]=cost[t][j]+c[t];
        }
    }
}

对于第二种情况: 其实这种情况和第一种情况差不多,设置weight[u]数组表示节点u的报酬,令从起点s到终点u的最大报酬为w[u],代码如下:

for(int j=1;j<=n;j++){
            if(!used[j]) 
            {
                if(d[j]>d[t]+g[t][j]){//距离不相同,说明路径唯一
                    w[j]=weight[j]+w[t];
                    d[j]=d[t]+g[t][j];
                }else if(d[j]==d[t]+g[t][j]&&w[j]<weight[j]+w[t]) 
                {//距离相同,说明路径不唯一,更新最大报酬
                    w[j]=weight[j]+w[t];
                }
            }
            
        }

对于第三种情况:只需要增加一个数组num[],令从起点s到终点u的最短路径条数为num[u]。初始化时只有num[s]=1,其余都为0,相关代码为:

int num[N];
        memset(num,0,sizeof num);
        num[1]=1;
        for(int j=1;j<=n;j++){
            if(!used[j]) 
            {
                if(d[j]>d[t]+g[t][j]){//距离不相同,说明路径唯一
                    w[j]=weight[j]+w[t];
                    num[j]=num[t];
                }else if(d[j]==d[t]+g[t][j]&&w[j]<weight[j]+w[t]) 
                {//距离相同,说明路径不唯一,更新最短路径条数
                    num[j]+=num[t]; //...t->j的最短路径有num[t]条,...其他->j的有num[j]
                }
            }
            
        }
    }

将上面的知识点总结起来的题为:【PAT A1003】Emergency

# include<cstring>
# include<iostream>
# include<algorithm>
using namespace std;
const int N=510;
int  n,m,s,v;
int d[N],g[N][N],weight[N],w[N],used[N],num[N];
int i,j;

void Dijkstra()
{
    d[s]=0;
    num[s]=1;
    w[s]=weight[s];
    for(i=0;i<n;i++)
    {
        int t=-1,MIN=0x3f3f3f3f;
        for(j=0;j<n;j++)
        {
            //找到最短点
            if(!used[j]&&d[j]<MIN){
                t=j;
                MIN=d[j]; 
            }
        }
        used[t]=1;
        //更新
        for(j=0;j<n;j++){
            if(!used[j]){
                if(d[j]>d[t]+g[t][j]){d[j]=d[t]+g[t][j]; w[j]=w[t]+weight[j];num[j]=num[t];}
                else if(d[j]==d[t]+g[t][j])
                { 
                    if(w[j]<w[t]+weight[j]){w[j]=w[t]+weight[j];}//最大报酬
                    num[j]+=num[t];
                }
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>m>>s>>v;
    memset(d,0x3f,sizeof d);
    memset(g,0x3f,sizeof g);
    for(i=0;i<n;i++) cin>>weight[i];
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b]=g[b][a]=min(g[a][b],c);
    }
    Dijkstra();
    cout<<num[v]<<' '<<w[v]<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一——一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值