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 + 第二标尺(借助额外的数组)
碰到有两条及以上可以达到最短距离的路径,题目就会给出第二个标尺(第一标尺是距离),要求在所有最短路径中选择第二标尺最优的一条路径。而第二标尺常见的是以下三种出题方法或其组合:
- 给每个边再增加一个边权(比如说花费),然后要求在最短路径有多少条时路径上的花费之和最小(如果边权是其他含义,也可以是最大)
- 给每个点增加一个点权(例如每个城市能收集到的物资),然后在最短路径有多少条时,要求路径上的点权之和最大(如果点权是其他含义的话也可以是最小)。
- 直接问有多少条最短路径。
对这三种出题方法,都只需要增加一个数组来存放新增的边权或点权或最短路径条数,然后在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算法记录所有最短路径
- 由于此时要记录所有最短路径,因此每个结点就会存在多个前驱结点,定义vector<int> pre[MAXN],(其实类似于图、树的邻接表写法),这样对每个结点v来说,pre[v]就是一个边长数组vector,里面用来存放结点v的所有能产生最短路径的前驱结点。
- 在本处的Dijkstra算法部分,只需要考虑距离这一个因素,因此不必考虑第二标尺的干扰,而专心于pre数组的求解。
- 对于结点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;
}