雜談:編程解決水管工遊戲

雜談:編程解決水管工問題

程序設計是一門極難上手的技能,僅僅憑着課堂上的知識,只能是熟悉一門編程語言的語法。但要是用計算機來解決一些實際的問題,哪怕是智力問題,課本上的知識是遠遠不夠的。
編程就像學游泳。學游泳一定要在水裏學,要在水裏摸索體會。學編程也是如此。
下面給大家帶來一個有趣的小問題,希望大家能夠學習到其中的程序設計思維與方法,尤其是中間建立模型的過程,相當精彩,大家欣賞一下。

以下內容改編自《啊哈,算法》第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;
}

總結

學習編程的過程其實是一種修煉,不斷挑戰難題,挑戰自己,才能不斷提升自己的思維能力,鍛鍊自己的數理能力,
豐富自己的代碼能力和編程技巧。
這裏的水管工問題就是很有啓發性的例子。還有許多有趣的問題值得我們用計算機編程去解決,這樣我們可以從算法思維的角度考察問題,獲得靈感。
這樣的問題有很多,推薦大家一本書《算法趣題》,裏面收錄了很多有趣的問題,值得我們思考,並且在計算機上用代碼實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章