Dijkstra

1 基础

        Dijkstra算法本质上是贪心+BFS的策略,即以源点为起始,不断探测相邻的顶点,每次都选择当前路径最短的那个顶点,并对该顶点的所有出边做松弛操作,各个顶点的最短路径就会一直扩散下去,直到所有顶点都处理完毕。

1.1 例题链接

奇怪的是,在提交题目代码的时候,如果把下面代码中cin的部分全部换成scanf,杭电OJ上就会报超时和超内存的错误,这个是我现在仍然没有想明白的。

代码

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <climits>
using namespace std;
const int MAXN = 200;
const int INF = INT_MAX;   // 无穷设为很大的数, 用到了climits头文件
struct Edge{
    int to;   // 终点
    int length;   // 长度
    Edge(int t,int l):to(t),length(l){}
};
struct Point{
    int number;  // 点的编号
    int distance;  // 源点到该点距离
    Point(int n,int d):number(n),distance(d){}
    bool operator< (const Point& p) const{
        return distance > p.distance;  // 距离小的优先级高
    }
};
vector<Edge> graph[MAXN];  // 邻接表实现的图
int dis[MAXN];  // 源点到各点距离
void Dijkstra(int s){
    priority_queue<Point> myPriorityQueue;   // 借助优先队列,这是BFS思想的体现
    dis[s] = 0;
    myPriorityQueue.push(Point(s,dis[s]));
    while(!myPriorityQueue.empty()){
        int u = myPriorityQueue.top().number;  // 离源点最近的点
        myPriorityQueue.pop();
        for(int i=0;i<graph[u].size();i++){
            int v = graph[u][i].to;
            int d = graph[u][i].length;
            if(dis[v]>dis[u]+d){   // 进行松弛操作
                dis[v] = dis[u]+d;
                myPriorityQueue.push(Point(v,dis[v]));
            }
        }
    }
}
int main(){
    int n,m;
    while(cin>>n>>m){
        memset(graph,0,sizeof(graph));  // 图初始化
        fill(dis,dis + n, INF);   // 距离初始化为无穷
        while(m--){
            int from,to,length;
            cin>>from>>to>>length;
            graph[from].push_back(Edge(to,length));
            graph[to].push_back(Edge(from,length));
        }
        int s,t;   // 起点与终点
        cin>>s>>t;
        Dijkstra(s);
        if(dis[t]==INF){  // 终点不可达
            dis[t] = -1;
        }
        cout<<dis[t]<<endl;
    }
    return 0;
}

2 进阶1   Dijkstra + 第二标尺(借助额外的数组)

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

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

对这三种出题方法,都只需要增加一个数组来存放新增的边权或点权或最短路径条数,然后在DIjkstra算法中修改优化dis[v]的那个步骤即可,其他部分不需要改动。

2.1 例题 PAT A1003

#include <iostream>
#include <queue>
#include <vector>
#include <climits>
#include <cstring>
using namespace std;
const int MAXN=501;
const int INF=INT_MAX;
struct Point {
    int number;
    int distance;
    Point(int n,int d):number(n),distance(d) {}
    bool operator< (const Point& y) const {
        return distance > y.distance;
    }
};
struct Edge {
    int to;
    int length;
    Edge(int t,int l):to(t),length(l) {}
};
vector<Edge> graph[MAXN];
int dis[MAXN],teams[MAXN],hands[MAXN],ways[MAXN];
void Dijkstra(int s) {
    dis[s]=0;
    hands[s]=teams[s];
    ways[s]=1;
    priority_queue<Point> myQueue;
    myQueue.push(Point(s,dis[s]));
    while(!myQueue.empty()) {
        int u = myQueue.top().number;
        myQueue.pop();
        for(int i=0; i<graph[u].size(); i++) {
            int v = graph[u][i].to;
            int d = graph[u][i].length;
            if(dis[v]>dis[u]+d) {
                dis[v]=dis[u]+d;
                hands[v]=hands[u]+teams[v];
                ways[v]=ways[u];   // 这里ways[v]状态的更新是ways[u],而不是1
                myQueue.push(Point(v,dis[v]));
            } else if(dis[v]==dis[u]+d) {
                ways[v]+=ways[u];   // 这里ways[v]状态的更新是+=ways[u],而不是+=1
                if(hands[v]<hands[u]+teams[v]) {
                    hands[v]=hands[u]+teams[v];
                }
            }
        }
    }
}
int main() {
    int N,M,S,T;
    cin>>N>>M>>S>>T;
    memset(graph,0,sizeof(graph));
    fill(dis,dis+N,INF);
    fill(hands,hands+N,0);
    fill(ways,ways+N,0);
    for(int i=0; i<N; i++)
        cin>>teams[i];
    for(int i=0; i<M; i++) {
        int c1,c2,L;
        cin>>c1>>c2>>L;
        graph[c1].push_back(Edge(c2,L));
        graph[c2].push_back(Edge(c1,L));
    }
    Dijkstra(S);
    cout<<ways[T]<<" "<<hands[T]<<endl;
    return 0;
}

3 进阶2   Dijkstra + 第二标尺 (借助DFS)

        先在Dijkstra算法中记录下所有最短路径(只考虑距离),然后从这些最短路径中选出一条第二标尺最优的路径(因为在给定一条路径的情况下,针对这条路径的信息都可以通过边权和点权很容易计算出来)。

3.1 使用Dijkstra算法记录所有最短路径

  1. 由于此时要记录所有最短路径,因此每个结点就会存在多个前驱结点,定义vector<int> pre[MAXN],(其实类似于图、树的邻接表写法),这样对每个结点v来说,pre[v]就是一个边长数组vector,里面用来存放结点v的所有能产生最短路径的前驱结点。
  2. 在本处的Dijkstra算法部分,只需要考虑距离这一个因素,因此不必考虑第二标尺的干扰,而专心于pre数组的求解。
  3. 对于结点v来说,由于每次找到更优的前驱时都会清空pre[v],因此pre数组不需要初始化。

3.2 遍历所有最短路径,找出一条使第二标尺最优的路径

根据pre数组遍历所有最短路径的过程,就会产生一个递归树,每次到达叶结点,就会产生一条完整的最短路径。

3.3 例题 PAT A1030

#include <iostream>
#include <queue>
#include <vector>
#include <climits>
#include <cstring>
#include <string>
#include <sstream>
using namespace std;
const int MAXN=501;
const int INF=INT_MAX;
struct Edge {
    int to;
    int length;
    int cost;
    Edge(int t,int l,int c):to(t),length(l),cost(c) {}
};
struct PreEdge {
    int pre;
    int cost;
    PreEdge(int p,int c):pre(p),cost(c) {}
};
struct Point {
    int number;
    int distance;
    Point(int n,int d):number(n),distance(d) {}
    bool operator< (const Point& y) const {
        return distance > y.distance;
    }
};
vector<Edge> graph[MAXN];
vector<PreEdge> pre[MAXN];
int dis[MAXN],minCost=INF;
string bestPath;
string Tran(int x) {
    string temp;
    stringstream ss;
    ss<<x;
    ss>>temp;
    ss.str("");
    return temp;
}
void Dijkstra(int s) {
    dis[s]=0;  // s结点没有前驱
    priority_queue<Point> myQueue;
    myQueue.push(Point(s,dis[s]));
    while(!myQueue.empty()) {
        int u = myQueue.top().number;
        myQueue.pop();
        for(int i=0; i<graph[u].size(); i++) {
            int v = graph[u][i].to;
            int d = graph[u][i].length;
            int c = graph[u][i].cost;
            if(dis[v]>dis[u]+d) {
                dis[v]=dis[u]+d;
                pre[v].clear();
                pre[v].push_back(PreEdge(u,c));
                myQueue.push(Point(v,dis[v]));
            } else if(dis[v]==dis[u]+d) {
                pre[v].push_back(PreEdge(u,c));
            }
        }
    }
}
void DFS(int s,int t,int totalCost,string path) {
    if(s==t) { // 到达叶结点
        if(totalCost<minCost) {
            minCost = totalCost;
            bestPath=path;
        }
        return ;
    } else {
        for(int i=0; i<pre[t].size(); i++) {

            DFS(s,pre[t][i].pre,totalCost+pre[t][i].cost,Tran(pre[t][i].pre)+" "+path);
        }
    }
}
int main() {
    int N,M,S,T;
    cin>>N>>M>>S>>T;
    fill(dis,dis+N,INF);
    memset(graph,0,sizeof(graph));
    for(int i=0; i<M; i++) {
        int from,to,len,cost;
        cin>>from>>to>>len>>cost;
        graph[from].push_back(Edge(to,len,cost));
        graph[to].push_back(Edge(from,len,cost));
    }
    Dijkstra(S);
    DFS(S,T,0,Tran(T)+" ");
    cout<<bestPath<<dis[T]<<" "<<minCost<<endl;
    return 0;
}

3.4 例题 PAT A1018

公共自行车管理中心(PBMC):编号0

N个站点(station):编号1~N

点权(对于编号1~N来说):当前拥有的自行车数量

边权:从一个点到达另一个点需要花费的时间

题意:给定站点T,需要找到一条从PBMC到站点T的耗时最小的路径;对这条路径上所有的结点权值进行调整,使每个点的权值达到最大容量的一半;若存在多条路径,第二标尺为PBMC需要“带出”的最少自行车数量,第三标尺为PBMC需要“带回”的最少自行车数量。

这题提交代码时,DFS函数需要用到参数stackLevel,将路径存放在path数组,在s==t时,沿着路径正向计算第二、三标尺需要的指标。(若用stack变量存储路径,然后放在DFS函数参数里,这个做法会导致超时)

通过测试用例的代码

#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <climits>
#include <cstring>
#include <sstream>
using namespace std;
const int MAXN=501;
const int INF=INT_MAX;
struct Edge {
    int to;
    int length;
    Edge(int t,int l):to(t),length(l) {}
};
struct Point {
    int number;
    int distance;
    Point(int n,int d):number(n),distance(d) {}
    bool operator< (const Point& y) const {
        return distance > y.distance;
    }
};
vector<Edge> graph[MAXN];
vector<int> pre[MAXN];
int dis[MAXN],val[MAXN],path[MAXN],resSend=INF,resBack=INF;
string resPath="";
string Tran(int x) {
    string temp;
    stringstream ss;
    ss<<x;
    ss>>temp;
    return temp;
}
void Dijkstra(int s) {
    dis[s]=0;
    priority_queue<Point> myQueue;
    myQueue.push(Point(s,dis[s]));
    while(!myQueue.empty()) {
        int u=myQueue.top().number;
        myQueue.pop();
        for(int i=0; i<graph[u].size(); i++) {
            int v = graph[u][i].to;
            int d = graph[u][i].length;
            if(dis[v]>dis[u]+d) {
                dis[v]=dis[u]+d;
                pre[v].clear();
                pre[v].push_back(u);
                myQueue.push(Point(v,dis[v]));
            } else if(dis[v]==dis[u]+d) {
                pre[v].push_back(u);
            }
        }
    }
}
void DFS(int s,int t,int cap,int stackLevel) {
    if(s==t) {
        int toSend=0,cur=0;
        string resP = Tran(s);
        for(int i=stackLevel-1; i>=1; i--) {
            int node = path[i];
            resP = resP + "->" + Tran(node);
            if(val[node]<cap/2) {
                if(val[node]+cur>=cap/2) {
                    cur-=(cap/2-val[node]);
                } else {
                    toSend+=(cap/2-val[node]-cur);
                    cur=0;
                }
            } else if(val[node]>cap/2) {
                cur+=(val[node]-cap/2);
            }
        }
        if(toSend<resSend) {
            resSend = toSend;
            resBack = cur;
            resPath = resP;
        } else if(toSend==resSend && cur<resBack) {
            resBack = cur;
            resPath = resP;
        }
    } else {
        for(int i=0; i<pre[t].size(); i++) {
            path[stackLevel] = t;
            DFS(s,pre[t][i],cap,stackLevel+1);
        }
    }
}
int main() {
    int capacity,N,T,M;
    cin>>capacity>>N>>T>>M;
    fill(dis,dis+N+1,INF);
    memset(graph,0,sizeof(graph));
    for(int i=1; i<=N; i++)
        cin>>val[i];
    for(int i=0; i<M; i++) {
        int from,to,time;
        cin>>from>>to>>time;
        graph[from].push_back(Edge(to,time));
        graph[to].push_back(Edge(from,time));
    }
    Dijkstra(0);
    DFS(0,T,capacity,1);
    cout<<resSend<<" "<<resPath<<" "<<resBack<<endl;
    return 0;
}

未通过测试用例的代码

(仅未通过一个测试点,目前暂时没办法找到逻辑出问题的地方;但是该代码比前面那个代码的时间复杂度要低一些;先在这里做个记录,以后如果能力提升了再回头看看能不能解决)

#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <climits>
#include <cstring>
#include <sstream>
using namespace std;
const int MAXN=501;
const int INF=INT_MAX;
struct Edge {
    int to;
    int length;
    Edge(int t,int l):to(t),length(l) {}
};
struct Point {
    int number;
    int distance;
    Point(int n,int d):number(n),distance(d) {}
    bool operator< (const Point& y) const {
        return distance > y.distance;
    }
};
vector<Edge> graph[MAXN];
vector<int> pre[MAXN];
int dis[MAXN],val[MAXN],resSend=INF,resBack=INF;
string resPath="";
string Tran(int x) {
    string temp;
    stringstream ss;
    ss<<x;
    ss>>temp;
    ss.str("");
    return temp;
}
void Dijkstra(int s) {
    dis[s]=0;
    priority_queue<Point> myQueue;
    myQueue.push(Point(s,dis[s]));
    while(!myQueue.empty()) {
        int u=myQueue.top().number;
        myQueue.pop();
        for(int i=0; i<graph[u].size(); i++) {
            int v = graph[u][i].to;
            int d = graph[u][i].length;
            if(dis[v]>dis[u]+d) {
                dis[v]=dis[u]+d;
                pre[v].clear();
                pre[v].push_back(u);
                myQueue.push(Point(v,dis[v]));
            } else if(dis[v]==dis[u]+d) {
                pre[v].push_back(u);
            }
        }
    }
}
void DFS(int s,int t,int cap,int &toSend,int &cur,int stackLevel,string& path) {
    if(s==t) {
        toSend=0,cur=0,path=Tran(s);
    } else {
        for(int i=0; i<pre[t].size(); i++) {
            int node = t;
            DFS(s,pre[t][i],cap,toSend,cur,stackLevel+1,path);
            path = path+"->"+Tran(node);
            if(val[node]<cap/2) {
                if(val[node]+cur>=cap/2) {
                    cur-=(cap/2-val[node]);
                } else {
                    toSend+=(cap/2-val[node]-cur);
                    cur=0;
                }
            } else if(val[node]>cap/2) {
                cur+=(val[node]-cap/2);
            }
            if(stackLevel==1) {
                if(toSend<resSend) {
                    resSend = toSend;
                    resBack = cur;
                    resPath = path;
                } else if(toSend==resSend && cur<resBack) {
                    resBack = cur;
                    resPath = path;
                }
            }
        }
    }
}
int main() {
    int capacity,N,T,M;
    cin>>capacity>>N>>T>>M;
    fill(dis,dis+N+1,INF);
    memset(graph,0,sizeof(graph));
    for(int i=1; i<=N; i++)
        cin>>val[i];
    for(int i=0; i<M; i++) {
        int from,to,time;
        cin>>from>>to>>time;
        graph[from].push_back(Edge(to,time));
        graph[to].push_back(Edge(from,time));
    }
    Dijkstra(0);
    int elem1=0,elem2=0;
    string elem3="";
    DFS(0,T,capacity,elem1,elem2,1,elem3);
    cout<<resSend<<" "<<resPath<<" "<<resBack<<endl;
    return 0;
}

3.5 例题 PAT A1072

将N个house编码为1~N,M个station编码为N+1~N+M(用myMap变量保存编码)

DMAX:station能辐射到的服务范围

边权:两个点之间的路径长度(这里需要注意以double类型存放

对每个station,分别以该station作为源点,进行一次Dijkstra算法后,若存在dis[i]>DMAX  (1<=i<=N),则该station不满足要求;反之,求出dis[i]  (1<=i<=N)  的最小值和平均值,加入候选队列

对所有满足要求的station,第一标尺为选择最小值最大的station,第二标尺为选择平均值最小的station,第三标尺为选择编码最小的station

#include <iostream>
#include <queue>
#include <vector>
#include <climits>
#include <cstring>
#include <map>
#include <sstream>
#include <cstdio>
using namespace std;
const int MAXN=1e3+15;
const int INF=INT_MAX;
struct Station {
    string name;
    int label;
    double avg;
    double minDis;
    Station() {}
    Station(string n,int l,double a,double m):name(n),label(l),avg(a),minDis(m) {}
    bool operator< (const Station& y) const {
        if(minDis!=y.minDis)
            return minDis < y.minDis;
        if(avg!=y.avg)
            return avg > y.avg;
        return label > y.label;
    }
};
struct Edge {
    int to;
    double length;
    Edge(int t,double l):to(t),length(l) {}
};
struct Point {
    int number;
    double distance;
    Point(int n,double d):number(n),distance(d) {}
    bool operator< (const Point& y) const {
        return distance > y.distance;
    }
};
vector<Edge> graph[MAXN];
map<string, int> myMap;
double dis[MAXN],DMAX;
int N,M,K;
string Tran(int x) {
    stringstream ss;
    string temp;
    ss<<x;
    ss>>temp;
    ss.str("");
    return temp;
}
void Dijkstra(int s) {
    fill(dis,dis+M+N+1,INF);
    dis[s]=0;
    priority_queue<Point> myQueue;
    myQueue.push(Point(s,dis[s]));
    while(!myQueue.empty()) {
        int u=myQueue.top().number;
        myQueue.pop();
        for(int i=0; i<graph[u].size(); i++) {
            int v = graph[u][i].to;
            int d = graph[u][i].length;
            if(dis[v]>dis[u]+d) {
                dis[v]=dis[u]+d;
                myQueue.push(Point(v,dis[v]));
            }
        }
    }
}
int main() {
    cin>>N>>M>>K>>DMAX;
    memset(graph,0,sizeof(graph));
    for(int i=1; i<=N; i++)
        myMap[Tran(i)]=i;
    for(int i=1; i<=M; i++)
        myMap["G"+Tran(i)] = i+N;
    for(int i=1; i<=K; i++) {
        string p1,p2;
        double dist;
        cin>>p1>>p2>>dist;
        graph[myMap[p1]].push_back(Edge(myMap[p2],dist));
        graph[myMap[p2]].push_back(Edge(myMap[p1],dist));
    }
    priority_queue<Station> station;
    for(int i=1; i<=M; i++) {
        Dijkstra(myMap["G"+Tran(i)]);
        int flag=1,minIndex=1;
        double sum=0;
        for(int j=1; j<=N; j++) {
            if(dis[j]>DMAX) {
                flag=0;
                break;
            }
            if(dis[j]<dis[minIndex])
                minIndex = j;
            sum+=dis[j];
        }
        if(flag==1) {
            station.push(Station("G"+Tran(i),i,sum/N,dis[minIndex]));
        }
    }
    if(station.empty())
        cout<<"No Solution"<<endl;
    else {
        Station elem = station.top();
        station.pop();
        printf("%s\n%.1lf %.1lf\n",elem.name.c_str(),elem.minDis,elem.avg);
    }
    return 0;
}

3.6 例题 PAT A1087 All Roads Lead to Rome

这道题目的测试点2暂时还未通过

https://blog.csdn.net/qq_22194315/article/details/54897935

https://blog.csdn.net/qq_44910084/article/details/106062894

https://blog.csdn.net/linsheng9731/article/details/40295615

#include <iostream>
#include <queue>
#include <vector>
#include <climits>
#include <cstring>
#include <map>
#include <sstream>
using namespace std;
const int MAXN=201;
const int INF=INT_MAX;
struct Edge {
    int to;
    int length;
    Edge(int t,int l):to(t),length(l) {}
};
struct Point {
    int number;
    int distance;
    Point(int n,int d):number(n),distance(d) {}
    bool operator< (const Point& y) const {
        return distance > y.distance;
    }
};
map<int,string> myMap2;
vector<Edge> graph[MAXN];
vector<int> pre[MAXN];
int dis[MAXN],happiness[MAXN];
int waysNum=0,resHap=INT_MIN,resNodeNum=0;
string resPath;
void Dijkstra(int s) {
    dis[s]=0;
    priority_queue<Point> myQueue;
    myQueue.push(Point(s,dis[s]));
    while(!myQueue.empty()) {
        int u = myQueue.top().number;
        myQueue.pop();
        for(int i=0; i<graph[u].size(); i++) {
            int v = graph[u][i].to;
            int d = graph[u][i].length;
            if(dis[v]>dis[u]+d) {
                dis[v]=dis[u]+d;
                pre[v].clear();
                pre[v].push_back(u);
                myQueue.push(Point(v,dis[v]));
            } else if(dis[v]==dis[u]+d) {
                pre[v].push_back(u);
            }
        }
    }
}
void DFS(int s,int t,int hap,int nodeNum,string path) {
    if(s==t) {
        waysNum++;
        if(hap>resHap) {
            resHap = hap;
            resNodeNum = nodeNum;
            resPath = path;
        } else if(hap==resHap && nodeNum<resNodeNum) {
            resNodeNum = nodeNum;
            resPath = path;
        }
    } else {
        for(int i=0; i<pre[t].size(); i++) {
            int node = pre[t][i];
            DFS(s,node,hap+happiness[node],nodeNum+1,myMap2[node]+"->"+path);
        }
    }
}
int main() {
    int N,K,S,T;
    string StartName;
    cin>>N>>K>>StartName;
    fill(dis,dis+N+1,INF);
    memset(graph,0,sizeof(graph));
    map<string,int> myMap;
    myMap[StartName]=1;
    myMap2[1] = StartName;
    S = myMap[StartName];
    happiness[1]=0;
    for(int i=2; i<=N; i++) {
        string CityName;
        cin>>CityName;
        myMap[CityName]=i;
        myMap2[i] = CityName;
        if(CityName=="ROM")
            T = myMap[CityName];
        cin>>happiness[i];
    }
    for(int i=1; i<=K; i++) {
        string c1,c2;
        int cost;
        cin>>c1>>c2>>cost;
        graph[myMap[c1]].push_back(Edge(myMap[c2],cost));
        graph[myMap[c2]].push_back(Edge(myMap[c1],cost));
    }
    Dijkstra(S);
    DFS(S,T,happiness[T],0,myMap2[T]);
    cout<<waysNum<<" "<<dis[T]<<" "<<resHap<<" "<<resHap/resNodeNum<<endl<<resPath<<endl;
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值