八皇后問題

       八皇后問題是經典的遞歸問題,題目的要求是這樣的:在國際象棋中,皇后是最強大的棋子,它可以吃掉任何跟自己同行,同列,或同一斜線上的棋子。求如何排列皇后,使得每一行都有一個皇后又讓每一個皇后都不能互相攻擊,給出所有的排列可能。

       首先我們做一些基礎工作:

1、 用一個二維數組來表示棋盤,“0”代表該位置無棋子,“1”代表有皇后;

2、 我們要能夠判斷一個位置是否能夠放置皇后(即檢測該點同行,同列或同一斜線上有無皇后);

3、 要能夠輸出一個棋盤;

       根據以上的要求,我們先編寫進行非核心段卻又必不可少的代碼:

(1)判斷一個位置能否放置皇后的代碼:

int isSafe(int (* chess)[EIGHT], int row, int col) {
    int i;
    int j;

    for (i = row - 1; i >= 0; i--) {
        if (chess[i][col] == 1) {
            return 0;
        }
    }
    for (i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
        if (chess[i][j] == 1) {
            return 0;
        }
    }
    for (i = row - 1, j = col + 1; i >= 0  && j < EIGHT; i--, j++) {
        if (chess[i][j] == 1) {
            return 0;
        }
    }
    return 1;
}
       從以上的代碼我們觀察到,僅僅是進行了三個方向的判斷,並沒有進行六個方向的判斷,這是因爲我們處理八皇后問題時是逐行箱向下嘗試的,當本行沒有放置皇后時,下一行是不可能放有皇后的,只有當在本行放置了一個皇后才能在下一行放置皇后,這就使得我們判斷一個位置能否放置皇后時只需要考慮它的正上方,左斜上方和右斜上方。
(2)輸出棋盤
void showChess(int(*chess)[EIGHT]) {
	int i;
	int j;

        printf("\n");
	for (i = 0; i < EIGHT; i++) {
		for (j = 0; j < EIGHT; j++) {
			printf("%d", chess[i][j]);
		}
		printf("\n");
	}
}

       接下來我們就開始着手解決如何找出所有可能性的八皇后排列問題:

       八皇后問題最關鍵的問題是如何能夠遍歷所有的情況,國際象棋的棋盤有八行八列,也就是說要嘗試8^8種情況,如果用循環實現的話,效果可想而知。

       我們既然想要用遞歸的方式實現八皇后問題,就要找到每一次函數調用的普適性規律,只有保證每一步都是相同的操作,才能夠使用遞歸調用函數。通過觀察我們發現,要解決八皇后問題,每一行都應該放置且只放置一個皇后,而每一個位置都應該嘗試去放一個皇后,再進行判斷這樣放置是否合理。如果一個位置可以放置皇后了,我們又要看下一行能否放置皇后且不衝突,當判斷完該行某一位置能夠放置皇后了,就應該繼續嘗試下一行,當之後的所有行都嘗試完畢了,再進行該行的下一個點的嘗試。

       總結以上的內容可以得出這樣的結論:

1、 處理八皇后問題應該是以行爲單位逐行測試的;

      

2、 每一行的處理都應該是相同的;

3、 每一個位置的檢測都應該是:放置一個皇后-判斷是否可以放置皇后-進行下一行的測試,看當前的排列方式能否構成一種解決方案;

4、 當測試到第八行的時候,如果存在可以放置的皇后,則輸出當前的棋盤(當前函數的結束條件,說明找到了一種解決方案,可以進行下一種解決方案的嘗試了);

       

5、 當某一行測試到第八個位置的時候,依然沒有位置可以放置皇后,則放回上一行進行下一個點的嘗試(這一點在實際編程中不用考慮,因爲根據遞歸調用的性質,每一行的每一個位置的測試都包括該行的之後所有行所有的位置的測試,測試完一整行就代表這一行以下的所有行都已經測試完畢),當然應該返回上一層進行下一個點的測試。

       

         例如該圖的第六行所有位置都無法放置皇后,自然第七八行也沒必要測試了,因此對於這種排列情況的測試已經完畢,需要做的就是改變第五行的皇后的位置繼續測試。

         有了以上的分析我們就可以編寫八皇后問題的相關代碼了:

void eightQueen(int(*chess)[EIGHT], int row) {
	int col;

	if (row >= EIGHT) {
		showChess(chess);

		return;
	}

	for (col = 0; col < EIGHT; col++) {
		if (isSafe(chess, row, col)) {
			chess[row][col] = 1;
			eightQueen(chess, row + 1);
			chess[row][col] = 0;
		}
	}
}
        微易碼的顏值擔當朱某人在講八皇后問題時舉了這樣一個例子,我覺得非常有助於對遞歸的理解:在一個很大的廣場上擺着一個棋盤,你和其他的七個人各負責一行的測試,你自己本身就相當於是一個八皇后的核心函數,你要做的只是從第一個格子開始擺放皇后,擺放之前先向上方,左斜上方和右斜上方觀察有無皇后,若有皇后則嘗試下一個位置,放置好皇后了就通知下一行的人繼續測試有無皇后,當你測試完了所有的格子了你要讓負責你之前一行的人繼續進行測試。當你收到負責你的下一行的人測試完畢的消息後你也要繼續進行測試。

        通過以上的比喻,可以更加清晰地看出八皇后問題的本質,以及遞歸函數在編寫時簡單卻又不容易想到的編程思路。


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