杂谈:编程解决水管工游戏

杂谈:编程解决水管工问题

程序设计是一门极难上手的技能,仅仅凭着课堂上的知识,只能是熟悉一门编程语言的语法。但要是用计算机来解决一些实际的问题,哪怕是智力问题,课本上的知识是远远不够的。
编程就像学游泳。学游泳一定要在水里学,要在水里摸索体会。学编程也是如此。
下面给大家带来一个有趣的小问题,希望大家能够学习到其中的程序设计思维与方法,尤其是中间建立模型的过程,相当精彩,大家欣赏一下。

以下内容改编自《啊哈,算法》第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:\n");
        for (i = 1; i <= top; i++)
            printf("(%d, %d) ", s[i].x, s[i].y);
        printf("\n");
        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\n");
    }

    return 0;
}

总结

学习编程的过程其实是一种修炼,不断挑战难题,挑战自己,才能不断提升自己的思维能力,锻炼自己的数理能力,
丰富自己的代码能力和编程技巧。
这里的水管工问题就是很有启发性的例子。还有许多有趣的问题值得我们用计算机编程去解决,这样我们可以从算法思维的角度考察问题,获得灵感。
这样的问题有很多,推荐大家一本书《算法趣题》,里面收录了很多有趣的问题,值得我们思考,并且在计算机上用代码实现。

 

posted @ 2016-12-10 13:18  20155110wangyifan  阅读(240)  评论(0编辑  收藏  举报