中国大学MOOC-陈越、何钦铭-数据结构-2019春 07-图6 旅游规划 (25 分)
有了一张自驾旅游路线图,你会知道城市间的高速公路长度、以及该公路要收取的过路费。现在需要你写一个程序,帮助前来咨询的游客找一条出发地和目的地之间的最短路径。如果有若干条路径都是最短的,那么需要输出最便宜的一条路径。
输入格式:
输入说明:输入数据的第1行给出4个正整数N、M、S、D,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0~(N−1);M是高速公路的条数;S是出发地的城市编号;D是目的地的城市编号。随后的M行中,每行给出一条高速公路的信息,分别是:城市1、城市2、高速公路长度、收费额,中间用空格分开,数字均为整数且不超过500。输入保证解的存在。
输出格式:
在一行里输出路径的长度和收费总额,数字间以空格分隔,输出结尾不能有多余空格。
输入样例:
4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20
输出样例:
3 40
本题用dijkstra算法即可解决,其中的松弛操作需要加入对于道路的价格的修改。
以下解法使用邻接矩阵和优先队列进行优化。
核心步骤:
堆优化的dijkstra算法
准备工作
#define inf 0x3f3f3f3f
struct Graph {
int dis;
};
typedef struct Point {
int data;
int dis;
bool operator< (const Point & a) const {
return dis > a.dis; //重载,优先弹出距离最小的点
}
}point;
struct Graph graph[500][500];
bool visit[500]; //vis数组记录该点是否曾经作为基点
int dis[500]; //dis数组记录到起始点的距离
priority_queue q;
堆优化dijkstra
void dijkstra(int begin) {
memset(dis, inf, sizeof(dis)); //初始化距离
point x;
x.data = begin;
x.dis = 0; //将起始点推入优先队列
dis[begin] = 0;
q.push(x);
while (!q.empty()) {
point now;
now = q.top();
q.pop();
//printf("%d顶点出队\n", now.data);
if (visit[now.data] == 1) continue; //如果弹出的点visit=1,说明其在堆中被更新过,而更新后具有更小距离的点已被弹出,所以直接continue
else visit[now.data] = 1; //如果该点此前从未被访问过,则它将成为这轮循环的基点
int i;
for (i = 0; i < n; i++) {
if (graph[now.data][i].dis != inf && visit[i] == 0) { //如果一个点做过基点,那么它的距离已经是最小的,不用对它进行松弛操作
dis[i] = min(dis[i], dis[now.data] + graph[now.data][i].dis);
point next;
next.data = i;
next.dis = dis[i];
//printf("%d顶点入队\n", i);
q.push(next); //visit==0的点可能会多次被推入堆中,此步骤即对堆中点距离的更新,弹出的时候一定是最后一次更新后距离最小的点,此时会将其visit设为1,则以后再次弹出相同的点时会直接continue
}
}
}
}
代码:
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
int n, m, s, d;
struct Graph {
int dis;
int money;
};
typedef struct Point {
int data;
int dis;
bool operator< (const Point & a) const {
return dis > a.dis; //重载,优先弹出距离最小的点
}
}point;
struct Graph graph[500][500];
bool visit[500];
int dis[500], money[500];
priority_queue q;
void dijkstra(int begin);
int main() {
memset(graph, inf, sizeof(graph));
scanf("%d %d %d %d", &n, &m, &s, &d);
int i;
for (i = 0; i < n; i++) {
graph[i][i].dis = 0;
graph[i][i].money = 0;
}
for (i = 0; i < m; i++) {
int start, end, dis, money;
scanf("%d %d %d %d", &start, &end, &dis, &money);
graph[start][end].dis = dis;
graph[start][end].money = money;
graph[end][start].dis = dis;
graph[end][start].money = money;
}
dijkstra(s);
printf("%d %d\n", dis[d], money[d]);
}
void dijkstra(int begin) {
memset(dis, inf, sizeof(dis)); //初始化距离和花费
memset(money, inf, sizeof(money));
point x;
x.data = begin;
x.dis = 0; //将起始点推入优先队列
dis[begin] = 0;
money[begin] = 0;
q.push(x);
while (!q.empty()) {
point now;
now = q.top();
q.pop();
//printf("%d顶点出队\n", now.data);
if (visit[now.data] == 1) continue; //如果弹出的点visit=1,说明其在堆中被更新过,而更新后具有更小距离的点已被弹出,所以直接continue
else visit[now.data] = 1; //如果该点此前从未被访问过,则它将成为这轮循环的基点
int i;
for (i = 0; i < n; i++) {
if (graph[now.data][i].dis != inf && visit[i] == 0) { //如果一个点做过基点,那么它的距离已经是最小的,不用对它进行松弛操作
if (dis[i] == dis[now.data] + graph[now.data][i].dis) {
money[i] = min(money[i], money[now.data] + graph[now.data][i].money);
}
else if (dis[i] > dis[now.data] + graph[now.data][i].dis) {
money[i] = money[now.data] + graph[now.data][i].money; //对于价格的更新会视情况而定
}
dis[i] = min(dis[i], dis[now.data] + graph[now.data][i].dis);
point next;
next.data = i;
next.dis = dis[i];
//printf("%d顶点入队\n", i);
q.push(next); //visit==0的点可能会多次被推入堆中,此步骤即对堆中点距离的更新,弹出的时候一定是最后一次更新后距离最小的点,此时会将其visit设为1,则以后再次弹出相同的点时会直接continue
}
}
}
}