合工大 程序设计艺术 实验三 房间最短路径

题目

给顶一个内含阻碍墙的房间,求解出一条从起点到终点的最最短路径。房间的边界 固定在 x=0,x=10,y=0 和 y=10。起点和重点固定在(0,5)和(10,5)。房间里还有 0 到 18 个 墙,每个墙有两个门。输入给定的墙的个数,每个墙的 x 位置和两个门的 y 坐标区间, 输出最短路的长度。
在这里插入图片描述

解题思路

虽然在这个房间里可以用很多种走法,但实际上图的节点只有墙壁的端点和始末点就行了。就比如(4,2),(4,7),(4,9),(7,4.5)(4,2),(4,7),(4,9),(7,4.5)等点,然后把这些点相连建图就可以了。我们把起点终点和每堵墙的a1,b1,a2,b2都看做图上的顶点。要完成建图,我们需要找出所有连通的路径,也就是可以直接相邻的两个顶点,完成建边。我们需要判断两点间是否没有阻碍,利用一次函数,求出连结两点的线段与每堵墙所在直线的交点,然后判断交点是否在墙洞,如果都不会被所有墙挡,那么这两个点就可以建一条边,该边的权值就是这两点之间的距离,可以通过公式求出。最后利用弗洛伊德算法求出最短路径即可。

细节说明

直线求解
就是简单的一次函数
在这里插入图片描述

运行结果

在这里插入图片描述

测试数据

输入
2
4 2 7 8 9
7 3 4.5 6 7
输出
10.06

源代码

#include <iostream>
#include <math.h>
#include <algorithm>
using namespace std;
struct wall {//封装墙的横坐标和每个断点的纵坐标
	double x;
	double y[5];
}w[20];
int n;//墙壁数量
double e[85][85];//邻接矩阵
//判断墙wa1上的n1点,是否可以直接与墙wa2上的n2点连接
bool canLink(int wa1, int wa2, int n1, int n2) {
	if (wa2 - wa1 == 1) {//两堵墙相邻肯定可以连接
		return true;
	}
	//求出两点所在的直线信息
	double x1 = w[wa1].x, x2 = w[wa2].x;//横坐标
	double y1 = w[wa1].y[n1], y2 = w[wa2].y[n2];//纵坐标
	double k = (y2 - y1) / (x2 - x1);//斜率
	double b = y1 - x1 * k;//截距
	//判断是否可行
	//遍历wa1到wa2之间的所有墙壁
	for (int i = wa1 + 1; i < wa2; i++) {
		double y = k * w[i].x + b;//直线经过墙y的纵坐标
		//与墙i有交点,不可连接
		if (y<w[i].y[1] || y>w[i].y[2] && y<w[i].y[3] || y>w[i].y[4]) {
			return false;
		}
	}
	//没有交点,可连接
	return true;
}
//建边 连接墙wa1上的断点n1与墙wa2上的断点n2
void addEdge(int wa1, int wa2, int n1, int n2) {
	if (!canLink(wa1, wa2, n1, n2)) {//有阻挡不可直接连接
		return;
	}
	double x1 = w[wa1].x, x2 = w[wa2].x;//横坐标
	double y1 = w[wa1].y[n1], y2 = w[wa2].y[n2];//纵坐标
	double dist = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));//两点间距离
	e[wa1 * 4 + n1][wa2 * 4 + n2] = dist;
	e[wa2 * 4 + n2][wa1 * 4 + n1] = dist;//储存代表边的权值
}
int main() {
	cout << "输入墙的数量:";
	cin >> n;
	//初始化矩阵e,所有边都设为不通,权值无穷大
	memset(e, 127, sizeof(e));
	//输入墙的信息
	cout << "请输入墙的信息:" << endl;
	for (int i = 1; i <= n; i++) {
		cin >> w[i].x;
		for (int j = 1; j <= 4; j++) {
			cin >> w[i].y[j];
		}
	}
	//初始化起点与终点
	w[0].x = 0;
	w[++n].x = 10;
	for (int i = 1; i <= 4; i++) {
		w[0].y[i] = 5;
		w[n].y[i] = 5;
	}
	//建边
	for (int i = 0; i < n; i++) {//第i堵墙
		for (int j = i + 1; j <= n; j++) {//第堵墙到第j堵墙
			for (int k = 1; k <= 4; k++) {//第i堵墙上的第k个结点
				for (int t = 1; t <= 4; t++) {//第j堵墙上的第t个结点
					addEdge(i, j, k, t);
				}
			}
		}
	}
	for (int i = 0; i < 85; i++) {
		e[i][i] = 0;//对角线上为0
	}
	//弗洛伊德算法求最短路径
	for (int k = 1; k <= n * 4 + 4; k++) {
		for (int i = 1; i <= n * 4 + 4; i++) {
			for (int j = 1; j <= n * 4 + 4; j++) {
				e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
			}
		}
	}
	cout << "最短距离为:" << e[1][n * 4 + 1];
	return 0;
}

声明

此题思路来源于 洛谷 P1354

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
首先需要明确一下,动态规划是一种解决最优化问题的算法思想,其通常包括三个要素:状态定义、状态转移方程以及边界条件。而对于一个给定的多段图,最短路径问题可以通过动态规划来解决。 具体步骤如下: 1. 定义状态:设f[i]表示从起点到第i个点的最短路径长度。 2. 确定状态转移方程:对于任意一个点j,如果j在第k段,则有f[j] = min{f[i]+w(i,j)}(i∈k)。其中w(i,j)表示从点i到点j的边的权值。 3. 确定边界条件:f[起点] = 0,因为起点到起点的距离为0。 4. 按照拓扑排序的顺序,依次计算f[i]的值。 最终,f[终点]的值即为起点到终点的最短路径长度。 下面是一个简单的C语言实现: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXN 1000 #define INF 0x3f3f3f3f int g[MAXN][MAXN]; // 存储图的邻接矩阵 int f[MAXN]; // 存储从起点到每个点的最短路径长度 int n, m; // n表示点数,m表示边数 void dp() { memset(f, INF, sizeof(f)); // 初始化f数组为INF f[1] = 0; // 起点到起点的距离为0 for (int i = 2; i <= n; ++i) { for (int j = 1; j < i; ++j) { if (g[j][i] != INF) { // 如果存在一条从j到i的边 f[i] = f[i] < f[j] + g[j][i] ? f[i] : f[j] + g[j][i]; } } } } int main() { scanf("%d%d", &n, &m); memset(g, INF, sizeof(g)); // 初始化邻接矩阵为INF for (int i = 1; i <= m; ++i) { int u, v, w; scanf("%d%d%d", &u, &v, &w); g[u][v] = w; // 存储从u到v的边的权值 } dp(); // 求解最短路径 printf("%d\n", f[n]); // 输出起点到终点的最短路径长度 return 0; } ``` 注意:该实现仅适用于无向无环图,如果图中存在环,则需要进行拓扑排序。同时,如果图中存在负权边,则需要使用贝尔曼-福德算法或SPFA算法来求解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

桃气十足

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值