杂谈:编程解决水管工问题
程序设计是一门极难上手的技能,仅仅凭着课堂上的知识,只能是熟悉一门编程语言的语法。但要是用计算机来解决一些实际的问题,哪怕是智力问题,课本上的知识是远远不够的。
编程就像学游泳。学游泳一定要在水里学,要在水里摸索体会。学编程也是如此。
下面给大家带来一个有趣的小问题,希望大家能够学习到其中的程序设计思维与方法,尤其是中间建立模型的过程,相当精彩,大家欣赏一下。
以下内容改编自《啊哈,算法》第4章第6节
问题描述:
一块矩形土地被分为N*M的单位正方形,这块土地里埋设一些水管,水管将从坐标为(1,1)的矩形土地的左上角左部边缘,延伸到坐标为(N,M)的矩形土地的右下角右部边缘。
水管只有两种:
- 弯曲水管:L
- 直水管: ━
土地中还有障碍物(比如树木等)
每种水管占据一个单位正方形土地。可以旋转这些管道,使其构成一个管道系统,创造一条从(1,1)到(N,M)的连通
管道。有障碍物的方格里没有管道。
建模开始:
我们用数字0表示障碍物,1到6表示管道的六种不同的摆放方式(如下表)。
┗ | ┏ | ┓ | ┛ | ━ | ┃ |
---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 |
于是,程序的输入可以规定为:
第一行输入矩形土地的大小n,m。
接下来输入n行m列的数字,表示每个单位正方形土地中的管道情况(数字0表示障碍物,1到6表示管道)
样例输入为:
5 4
5 3 5 3
1 5 3 0
2 3 5 1
6 1 1 5
1 5 5 4
(自行脑补实际管道铺设情况)
程序要输出的是应该是铺设的路径,如果不存在这样的路径,则输出impossible。
样例输出:
The path is:
(1,1) (1,2) (2,2) (3,2) (3,3) (3,4) (4,4) (5,4)
(注:铺设管道的最左上角起点坐标为(1,1),最右下角终点坐标为(5,4),规定进水口在最左上角方格的左边,出水口在最右下角方格的右边,输出路径可能不唯一)
编程思路:
以下分析过程中的水管的图形与土地的状态请自行脑补
因为只有两种水管,直管(2种状态)和弯管(4种状态)。首先从(1,1) 开始尝试。(1,1)是直管,进水口又在
(1,1)的左边,因此(1,1)处的水管只能用5号摆放方式。
之后达到(1,2)。(1,2)处是弯管,进水口在(1,2)的左边,因此(1,2)有两种排放方式,分别是3号与4号。由于4号
摆放方式会出界,只能用3号摆放方式,从而来到了(2,2)。
(2,2)处是直管,进水口在上方,只能用6号摆放方式,接下来,来到(3,2)。
(3,2)是弯管,进水口在上面,有2种摆放方式可以选择,分别是1号和4号。
这两种选择都可以,我们就要分别去尝试……
依次类推,直到来到(n,m+1)为止,方案产生。
代码实现
这里用到了深度优先搜索DFS。当处在(x,y)处时,依次枚举当前管道的每一张摆放方式,但并非每一种都可以,还要
判断(x,y)处的进水口的方向。
这里规定进水口在左边用数字1表示,在上边用2表示,右边用3表示,下边用4表示。
要输出路径,只需要用一个栈存放相应的结点就可以了。
#include <stdio.h>
#define MAX_N 55
#define MAX_M 55
int a[MAX_N][MAX_M], book[MAX_N][MAX_M];
int n, m, flag = 0;
struct node {
int x;
int y;
}s[100];
int top = 0;
void dfs(int x, int y, int front) //x,y表示当前处理的位置坐标,front表示(x,y)进水口的方法
{
int i;
//判断是否达到终点
if (x == n && y == m + 1) {
flag = 1; //找到铺设方案
printf("The path is:
");
for (i = 1; i <= top; i++)
printf("(%d, %d) ", s[i].x, s[i].y);
printf("
");
return;
}
//判断是否越界
if (x < 1 || x > n || y < 1 || y > m)
return;
//判断(x,y)处是否已经遍历过
if (book[x][y] == 1)
return;
++top; s[top].x = x; s[top].y = y; //将当前坐标压栈
//当前水管是直管的情况
if (a[x][y] >= 5 && a[x][y] <= 6) {
if (front == 1) dfs(x, y+1, 1); //进水口在左边,只能用5号摆放方式
if (front == 2) dfs(x+1, y, 2); //进水口在上边,只能用6号摆放方式
if (front == 3) dfs(x, y-1, 3); //进水口在右边,只能用5号摆放方式
if (front == 4) dfs(x-1, y, 4); //进水口在下边,只能用6号摆放方式
}
//当前水管是弯管的情况
if (a[x][y] >= 1 && a[x][y] <= 4) {
//进水口在左边
if (front == 1) {
dfs(x+1, y, 2);
dfs(x-1, y, 4);
}
//进水口在上边
if (front == 2) {
dfs(x, y+1, 1);
dfs(x, y-1, 3);
}
//进水口在右边
if (front == 3) {
dfs(x-1, y, 4);
dfs(x+1, y, 2);
}
//进水口在下边
if (front == 4) {
dfs(x, y+1, 1);
dfs(x, y-1, 3);
}
}
book[x][y] = 0; //取消标记
top--; //将当前坐标从栈中弹出
return;
}
int main(void)
{
int i, j, num = 0;
while (scanf("%d %d", &n, &m) == 2) {
for (i = 1; i <= n; i++)
for (j = 1; j <= m; j++)
scanf("%d", &a[i][j]);
dfs(1, 1, 1);
if (flag == 0)
printf("impossible
");
}
return 0;
}
总结
学习编程的过程其实是一种修炼,不断挑战难题,挑战自己,才能不断提升自己的思维能力,锻炼自己的数理能力,
丰富自己的代码能力和编程技巧。
这里的水管工问题就是很有启发性的例子。还有许多有趣的问题值得我们用计算机编程去解决,这样我们可以从算法思维的角度考察问题,获得灵感。
这样的问题有很多,推荐大家一本书《算法趣题》,里面收录了很多有趣的问题,值得我们思考,并且在计算机上用代码实现。