算法报告四 Dijkstra算法(最短距离)

一、题目大意

给出一张图,并给定起点和终点,问起点到终点的最短距离是多少?
有两个特殊要求
1)如果从顶点i到顶点j有不止一条最短路径,那么输出路段数最少者;
2)如果具有最短路径并且路段数也是最少的路径至少有2条,那么输出按顶点编号的字典序最小者。

二、分析

因为所有路径没有负边权,所以这里求最短路用的是Dijkstra算法。
接下来简要的介绍一下Dijkstra。
Dijkstra算法的思想就是从已经处理好的离起点最短距离转移到未访问点到起点的最短路。有点dp的味道。
他用一个vis数组来区分这个点有没有被处理。
用dis[i]数组存起点到i这个点的最短距离。
用一个优先队列存储从以处理好的点组成的集合出发能到的点,类似于一个备选点,用优先队列维护其中离起点最近的那个点。
一步步递推,最终处理出所有点到源点的最短距离。
最后终点是什么就去找这个最短距离。

单纯找最短路径很简单。但怎么达到那两个额外的要求要转几个弯。
问题一:怎么找路段最少的最短路?
用一个ceng数组来记录起点到这个点经过几条边,类似于dinic分层图的感觉。

如果走多条边到一个点的dis值一致,那么就取层数最小的那个,并且记录前驱
问题二:怎么找字典序最小的多段路?
若是多段路的最少的路径有多条,怎么找出其中字典序最小的那个,我暂时没有想到什么比较巧妙的解法,就是最暴力的把可能的前驱全用set保存,最后一个dfs深搜,把所有可能的路径转为string类型,存在set里,那么set会自动排序,那么set中的第一个值就是字典序最小的答案。

三、代码

#include<iostream>
#include<stdio.h>
#include<string>
#include<cstring>
#include<vector>
#include<stack>
#include<algorithm>
#include<queue>
#include<set>
using namespace std;
#define clr(a, x) memset(a, x, sizeof(a))
#define mp(x, y) make_pair(x, y)
#define pb(x) push_back(x)
#define X first
#define Y second
#define fastin                    \
    ios_base::sync_with_stdio(0); \
    cin.tie(0);
typedef long long LL;
typedef pair<int, int> PII;
typedef vector<int> VI;
const int INF=0x3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-6;
const int N=3000;
const int M=71000;

set<int> pre[N];
int ceng[N];

int n,m;
int dis[N];
int vis[N];
struct edge
{
    int to,v;
};
struct qnode
{
    int id;
    int v;
    bool operator <(const qnode &r)const
    {
        return (v==r.v)?id>r.id:v>r.v;
    }
};
vector<edge> G[N];
void addedge(int u,int v,int w){
    G[u].push_back(edge{v,w});
}
void init(){
    for(int i=0;i<=n;i++) G[i].clear();
}
void Dij(int start)
{
    clr(vis,0);
    clr(dis,INF);
    clr(ceng,INF);
    for(int i=0;i<=n;i++) pre[i].clear();
    priority_queue<qnode> q;
    dis[start] = 0;
    ceng[start] =0;
    q.push(qnode{start,0});
    while(!q.empty()){
        qnode cur=q.top();
        q.pop();
        int u=cur.id;
        vis[u]=1;
        for(int i=0;i<G[u].size();i++){
            int v=G[u][i].to,w=G[u][i].v;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                q.push(qnode{v,dis[v]});
                pre[v].clear();
                pre[v].insert(u);
                ceng[v]=ceng[u]+1;
            }
            else if(dis[v]==dis[u]+w){
                if(ceng[v]>=ceng[u]+1){
                    pre[v].insert(u);
                    ceng[v]=ceng[u]+1;
                }
            }
        }
    }
}
/*
void print_ans(int s){
    if(pre[s]==-1) {
        cout<<s;
        return ;
    }
    print_ans(pre[s]);
    cout<<"->"<<s;
}
*/
set<string> ans;
void print_ans(int s,string ss){
    if(pre[s].empty()) ans.insert(ss);
    for(auto v:pre[s]){
        print_ans(v,char(v+'0')+ss);
        //cout<<v<<endl;
    }
}
int main(){
/*
    freopen("input_spf.txt","r",stdin);
    freopen("output_spf.txt","w",stdout);
*/
    int cas=0;
    while(~scanf("%d",&n)){
        init();

        for(int i=1;i<=n;i++)
        for(int j=1,x;j<=n;j++){
            cin>>x;
            if(x!=-1) addedge(i,j,x);
        }
        int s,t;
        cin>>s>>t;
        Dij(s);
        printf("Case %d\nThe least distance from %d->%d is %d\n",++cas,s,t,dis[t]);
        /*
        for(int i=1;i<=n;i++)
            cout<<i<<" "<<pre[i]<<endl;
        */
        ans.clear();
        string sss="";
        sss+=char(t+'0');
        printf("Path: ");
        print_ans(t,""+sss);
        for(auto v:ans) {sss=v;break;}
        for(int i=0;i<sss.size();i++) {
            printf("%c%c%c",sss[i],"- "[i==sss.size()-1],">\n"[i==sss.size()-1]);
        }
    }
    return 0;
}

四、体会

    这题基于最短路算法,要找出最小段数且取字典序最小的最短路。首先最短路是很简单的,直接Dijkstra寻找最短路,这题的打印路径有点烦,一开始我看到只需要输出一条段数最小的路径,没注意要按照字典序排序,就直接记录一个前驱,输出了一条路径,但是这条路径不一定是字典序最小的,你要从所有满足条件的路径中找到那条字典序最小的路径,之后总的方向就两种:1,根据某种性质,只记录字典序最小的点的前驱;2,把所有满足条件的路径全找出来,然后找出字典序最小的那个。
    一开始,我尝试了第一种想法,实在没有什么办法,之后采取了第二种办法,暴力找路径,存set自动排序,总的过程类似于实验二,找出所有的路径,取set中的第一个。

添加新评论