從經典八皇后問題看回溯演算法

2022-09-20 10:33:12 字數 2878 閱讀 9782

八皇后問題是乙個經典的問題,這個問題最早是在2023年,由國際西洋棋棋手馬克斯·貝瑟爾提出的,在兩個世紀之後的今天,依然被人們津津樂道。這個問題的描述如下:在8×8格的西洋棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問一共有多少種擺法。

在解八皇后問題之前,我們先來看這樣乙個例子,如下圖所示,是遊戲的小地圖:

現在你站在了路口a,前面有四個房間1、2、3、4。其中某乙個房間有寶箱,而沒有寶箱的房間也不會有陷阱和額外的敵人,如果是你,你會如何探索前面的區域?稍加思考便可以得到答案,既然不知道寶箱具體會在**,那麼最好的方法就是地毯式搜尋,去每個房間看一下,但這個過程要想節約跑路的時間,一般我們會這樣規劃路線:

(1)從路口a出發,經過路口c,先走向房間4

(2)若房間4沒有,那麼我們退回路口c,再去探索房間3

(3)若房間3沒有,則退回路口a,經過路口b,探索房間2

(4)若房間2還是沒有,則退回路口b,探索房間1

這樣的規劃能夠確保我們走最少的重複路線,其實這就是回溯法的核心思想了,當前選擇不是最優解或者無法達到目標時,就退回上一步重新選擇,若上一步還不行就繼續回溯上上步……,以此類推。

回溯法是一種搜尋方法,通過回溯的方法,能避免一些無意義的重複計算,例如在上面的例子中,如果是按照2-3-1-4的順序搜尋,那麼就會多走一段a到b和a到c的路程,但這段路程是可以用回溯思想的規劃避開的。

現在讓我們把目光重新放回到八皇后問題上,對於這個問題,如果不採用任何演算法思想而直接暴力列舉的話,總的解空間為64選8,即c(64,8)=64!/8!*(64-8)!,可以簡單寫個程式來計算答案(因為計算過程會發生溢位,所以為了方便,這裡就直接用python偷懶了)

def

fact(n):

if n==1:

return 1

else

:

return fact(n-1)*n

print(fact(64)/(fact(8)*fact(56)))

得到的答案為:4426165368,也就是44億種組合。很顯然,這是無法接受的。

現在來分析一下這個問題,既然我們知道所有的皇后不能在同一行、同一列和同一斜線上,那麼每行每列都只能有乙個皇后。因此我們採用逐行掃瞄的方式,一行一行地安排皇后。其基本思路為:

1.從第一行開始安排皇后

2.安排皇后時,從該行的第一列開始檢查,直到找到安全的位置為止。對於安全位置的具體檢查方法為:

(1)該列沒有其它皇后

(2)該列的左上角對角線上沒有其它皇后

(3)該列的右上角對角線上沒有其它皇后

3.若找到了安全位置,則在安全位置放置皇后,並開始安排下一層

4.若第八行已經安排好了,則說明找到了一組解,計數加一,然後重新嘗試第八行其它位置是否可行。

5.若該行沒找到安全位置,則回溯到上一行,移動上一行的皇后,重新開始安排,若上一行還是不行,則繼續回溯到上上行。

6.若回溯到第一行時,第一行所有的位置都已經放過了,證明我們已經遍歷完了所有的可能性,此時結束搜尋,輸出最終答案。

一般來說,對於八皇后問題這種8x8的矩陣,會想到用二維陣列來模擬,這肯定是可行的,但不是最優的。因為我們是逐行掃瞄的,所以其實我們需要的資訊是」前面行的皇后放置在哪一列了「。因此,只需要乙個長度為8的一維陣列,分別儲存第1到第8行皇后所在的列數。

按照之前的思路,要解決這個問題有兩個難點,一是檢查某行是否有安全位置,另乙個就是回溯的設計。

我們剛說的用一維陣列儲存每一行皇后所在的列數,陣列下標代表行數,該下標對應的元素為該行皇后所在的列數。當檢查第n行的安全位置時,思路是這樣的:

1,從第n行最左邊的一列開始,逐個往右檢查

2,檢查某一列是否可行時,需要遍歷從第一行到第n-1行的第1列到第8列,看看是否有會造成危險的皇后

定義乙個函式row_check()檢查某一行是否有安全位置,引數為行數,假設用來儲存皇后位置的陣列名為c,則c[row]表示當前嘗試擺放的皇后列數。偽**描述如下:

//

遍歷該行每一列

for(int col=0;col<8;col++)

//若檢查未通過,則進行下一次迴圈,嘗試下一列

}

那具體的row_check的函式實現為:

bool row_check(int

row)

return

true

;}

其中需要說明幾點,c[row]==c[i],表示當前嘗試的列數,在之前的第i行的同一列已經擺放過皇后了。而對角線的檢查是這樣的,把棋盤的行看作是x軸(i),列看作是y軸(c[i]),如果處於對角線則斜率為1或者-1,也就是y座標之差的絕對值與x座標之差的絕對值是相等的。即abs(c[row]-c[i])==abs(row-i)。

//

函式queen表示安排第row行皇后的位置

void queen(int

row)

else

//從第row行的第0列開始,逐個嘗試

for(int col=0;col<8;col++)

}

#include#include

using

namespace

std;

int total=0

;int c[8

];bool row_check(int

row)

return

true;}

void queen(int

row)

else

for(int col=0;col<8;col++)

}int

main()

經典回溯演算法 八皇后問題

八皇后問題是由19世紀數學家 搞死先生 高斯先生 提出的,具體的問題是這樣的 在西洋棋的棋盤中 有8 8格 擺放 8個皇后,這八個皇后不能相互攻擊到 皇后的攻擊方向很廣 橫著,豎著,斜著都能攻擊 即 8個皇后不能處於同行 同列 同一正反對角線上,這樣就不能相互攻擊到了。那麼,這樣的皇后佔位的方法,一...

經典回溯演算法(八皇后問題)

今天偶爾看到了乙個演算法問題 八皇后問題 回想一下還是在演算法課上學習過的,於是,自己總結了一下,寫了這篇日誌 演算法提出 在西洋棋棋盤上 8 8 放置八個皇后,使得任意兩個皇后之間不能在同一行,同一列,也不能位於同於對角線上。問共有多少種不同的方法,並且指出各種不同的放法。演算法思路 首先我們分析...

八皇后問題 經典回溯演算法

八皇后問題,是乙個古老而著名的問題,是回溯演算法的典型案例。該問題是國際西洋棋棋手馬克斯 貝瑟爾於1848年提出 在8 8格的西洋棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行 同一列或同一斜線上,問有多少種擺法。高斯認為有76種方案。1854年在柏林的象棋雜誌上不同的作者發表了...