程序设计思维Week2-作业
A-Maze
问题描述
输入一个5x5的二维数组由0、1组成,表示法阵地图。地图显示,0表示可以走,1表示不可以走,左上角是起点,右下角是终点,这两个位置保证为0。编写程序,找到起点到终点的最短路线。
要求输出若干行,表示从左上角到右下角的最短路径依次经过的坐标。数据保证有唯一解。
解法分析
该题是典型的BFS算法应用。由于题目要求输出最短路径依次经过的坐标,需要定义一个Point类的path数组用于记录路径,vis数组用于记录某个点是否已经遍历到。在扩展过程中,每个点可以向上下左右四个方向进行扩展,但这里需要注意的是,在BFS过程中并不是每一个遍历到的点都是在最短路径上的,因此在定义Point类时,需要额外的pre用于记录到达该点的路径上它之前的那个点在path数组中的位置。
不停地进行扩展直到遇到终点,利用终点的pre向前递归找到最短路径并输出坐标。
总结
该题属于BFS算法的简单变形,需要熟练掌握BFS的基本结构。
需要注意的是:
- BFS的扩展是遍历了所有可到达的点,但很多其他点并不在两点间的最短路径上,因此记录到这些点时应回溯到他之前的那个点。
- 对每个点在path数组中的位置确定,需要front、end,front用于确定该点之前的点的位置,end用于记录该点在数组中的位置
- 遍历的判断条件:坐标不应超出5x5且可到达而还未遍历到的点。遍历到终点后及时停止扩展。
代码
#include <iostream>
#include<queue>
using namespace std;
bool vis[5][5];
int a[5][5];
struct point {
int x;
int y;
int pre;
point() { }
point(int a, int b) {
x = a; y = b;
}
point(int a, int b, int p) {
x = a; y = b; pre = p;
}
};
queue<point> Q;
int dx[] = { 0,0,1,-1 };
int dy[] = { 1,-1,0,0 };
point path[25];
//回溯
void print(point n) {
if (n.pre != -1)print(path[n.pre]);
printf("(%d, %d)\n", n.x, n.y);
}
void bfs() {
memset(vis, 0, sizeof(vis));
int front = 0, end = 0;
Q.push(point(0, 0,-1));
point next,now;
vis[0][0] = 1;
path[end++] = point(0, 0,-1);
while (!Q.empty()) {
now = Q.front();
Q.pop();
front++;
if (now.x == 4 && now.y == 4) {
print(now); return;
}
//int count = 0;
for (int i = 0; i < 4; ++i) {
int x = now.x + dx[i], y = now.y + dy[i];
if (x < 0 || x>4 || y < 0 || y>4 || a[x][y] == 1)continue;
else if (!vis[x][y]) {
vis[x][y] = 1;
next.x = x, next.y = y;
next.pre = front - 1; //记录前一个节点
path[end++] = next;
Q.push(next);
}
}
}
}
int main()
{
for (int i = 0; i < 5; i++)
for (int j = 0; j < 5; j++)
{
cin >> a[i][j];
}
bfs();
}
B-Pour Water
问题描述
倒水问题:两个水杯,最大容量分别为A和B,起始两个水杯中都是空的,我们希望通过一系列操作得到容量为C的水。操作仅限于: 倒满A/B杯,倒空A/B杯,把A的水倒到B中或反之。
要求输出获得C体积水的每一个操作,“fill A” 表示倒满A杯,"empty A"表示倒空A杯,“pour A B” 表示把A的水倒到B杯并且把B杯倒满或A倒空。直到获得C体积的水,输出“success”。
ps:本题答案可能存在多解
解法分析
由于题目要求输出每步操作,因此我们需要自定义Status类,其中包括具体操作对应的序号以及本次操作后A和B杯中水的体积。类似于迷宫问题,我们可以把从起始状态到获得容量为C的水的一系列操作看成一条路径,即我们需要找到一条可以成功到达终点的路径,而路径可能有多条,所以可以使用BFS算法进行扩展,扩展方向为几种不同的操作。而同样,并不是扩展出的每一步操作都可以最终获得想要的结果,因此需要map类的from数组记录每一个状态与前一个状态的对应关系。
在扩展过程中使用队列进行BFS,根据不同的操作进行讨论直到获得想要的结果。再根据from数组递归至起始状态根据操作序号输出具体操作。
总结
该题属于隐式图问题,同样可以用BFS扩展,需要熟练掌握BFS的基本结构。
需要注意的点:
- 对于每一种状态来说只对应一种前状态,即在map类中该状态作为Key不可对应多个Value
- 并不是每一种状态最终都可以获得想要的结果,因此在遇到这些状态时应回溯到他之前的点然后继续其他的扩展,即摈弃无用的操作。
代码
#include <iostream>
#include<string>
#include <map>
#include<queue>
using namespace std;
struct Status
{
int a, b,m;
bool operator<(const Status &s) const
{
return a != s.a ? a < s.a : b < s.b;
}
};
queue<Status> Q;
map<Status, Status> from;
void print(Status &s) {
if (s.m == -2)return;
print(from[s]);
if (s.m == 1)printf("empty A\n");
else if(s.m==2)printf("empty B\n");
else if(s.m==3)printf("fill A\n");
else if (s.m == 4||s.m==5)printf("pour B A\n");
else if (s.m == 6)printf("fill B\n");
else if (s.m == 7||s.m==8)printf("pour A B\n");
}
void refresh(Status &s, Status &t) {
if (from.find(t) == from.end()) {
from[t] = s;
Q.push(t);
}
}
void bfs(int A, int B, int C)
{
Status s, t;
s.a = 0; s.b = 0; s.m = -2;
Q.push(s);
while (!Q.empty())
{
s = Q.front();
Q.pop();
if (s.a == C || s.b == C) {
print(s);
printf("success\n");
return;
}
//倒空a的水
if (s.a > 0) {
t.a = 0;
t.b = s.b;
t.m = 1;
refresh(s, t);
}
//倒空b的水
if (s.b > 0) {
t.b = 0;
t.a = s.a;
t.m = 2;
refresh(s, t);
}
//续满a
if (s.a < A)
{
t.a = A;
t.b = s.b;
t.m = 3;
refresh(s, t);
if (s.b != 0) {
if (s.a + s.b <= A) {
t.a = s.a + s.b;
t.b = 0;
t.m = 4;
refresh(s, t);
}
else {
t.a = A;
t.b = s.a + s.b - A;
t.m = 5;
refresh(s, t);
}
}
}
//续满b
if (s.b < B)
{
t.a = s.a;
t.b = B;
t.m = 6;
refresh(s, t);
if (s.a != 0)
{
if (s.a + s.b <= B)
{
t.a = 0;
t.b = s.a + s.b;
t.m = 7;
refresh(s, t);
}
else {
t.a = s.a + s.b - B;
t.b = B;
t.m = 8;
refresh(s, t);
}
}
}
}
//printf("-1/n");
}
int main()
{
int a, b, c;
while (cin >> a >> b >> c)
{
bfs(a, b, c);
}
return 0;
}