数据结构-2019春 07-图6 旅游规划 (25 分)

中国大学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<point> 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 <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <queue>
#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<point> 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
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值