Python實現八皇后問題(詳細註釋)

回溯法

在瞭解八皇后問題之前我們先了解什麼是回溯法,因爲八皇后問題是回溯法的一個經典算法習題,也是八皇后問題用到的主要算法。

根據百度百科解釋:回溯法(探索與回溯法)是一種選優搜索法,又稱爲試探法,按選優條件向前搜索,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步重新選擇,這種走不通就退回再走的技術爲回溯法,而滿足回溯條件的某個狀態的點稱爲“回溯點”。

舉個集合小例子:列舉集合 {1,2,3} 中所有子集的問題

使用回溯法。從集合的開頭元素開始,對每個元素都有兩種操作,直到集合最後一個元素。其中的每個操作都可以看作是一次嘗試,每次嘗試都可以得出一個結果。將得到的結果綜合起來,就是集合的所有子集。

在這裏插入圖片描述
回溯法和遞歸的聯繫:
遞歸是從問題的結果出發,例如求 n!,要想知道 n!的結果,就需要知道 n*(n-1)! 的結果,而要想知道 (n-1)! 結果,就需要提前知道 (n-1)*(n-2)!。這樣不斷地向自己提問,不斷地調用自己的思想就是遞歸。

回溯和遞歸唯一的聯繫就是,回溯法可以用遞歸思想實現

八皇后問題

八皇后問題是以國際象棋爲背景的問題:有八個皇后(可以當成八個棋子),如何在 8*8 的棋盤
中放置八個皇后,使得任意兩個皇后都不在同一條橫線、縱線或者斜線上。

在這裏插入圖片描述

算法思路:

  1. 從棋盤的第一行開始,從第一個位置開始,依次判斷當前位置是否能夠放置皇后。
    判斷的依據爲:同該行之前的所有行中的皇后所在位置進行比較,如果在同一列,或者在同一條對角線上(正方形的兩條對角線),都不符合條件,繼續檢查後序位置;
  2. 如果i該行所有位置都不符合要求,則回溯到前一行,改變皇后的位置,繼續i試探;
  3. 如果試探到最後一行,所有皇后位置擺放完畢,則直接打印出8*8棋盤。最後將棋盤恢復原樣,避免影響下一次擺放。

實現代碼:

代碼分爲三部分:

  1. conflict 函數,判斷下一行皇后位置是否與之前皇后的位置衝突
  2. queens函數,採用生成器的方式來產生每一個皇后的位置,並用遞歸思想實現回溯法計算出每一種結果的皇后的位置
  3. prettyprint函數,友好展示棋盤,畫出每一種結果的皇后的位置
def conflict(state, nextColumn):
    """
    判斷是否衝突
    因爲座標是從0開始的,所以state的長度代表了下一行的行座標
    :param state:(7,4,6,0,2) 標記每行皇后所在的位置 (0,7)一行八列 (2,4) (3,6) (4,0) (5,2)
    :param nextColumn:下一行的列座標
    :return:
    """
    nextRow = rows = len(state) # 5
    for row in range(rows):   #  0,1,2,3,4
        # 獲取當前行的列
        column = state[row]
        """
        如何判斷是否衝突:
            1. 如果列的差值爲0,說明兩皇后在同一列
            2. 如果列的差值等於行的差值,說明兩皇后在對角線上
        """
        if abs(column - nextColumn) in [0, nextRow - row]:
            return True
    return False

# 採用生成器的方式來產生每一個皇后的位置,並用遞歸來實現下一個皇后的位置
def queens(num, state=()):
    """
    基於遞歸採用回溯算法,算出每一種結果

    :param num: 皇后的數量  8
    :param state: 列座標。初始爲空。參數爲元組不爲列表,因爲參數只能爲不可變數據類型
    :return:
    """
    # 每一行的列座標都是從0:7的
    # 0,1,2,3,4,5,6,7
    for pos in range(num):
        # 默認state爲空。長度爲0,但是是不衝突的
        # 判斷是否衝突,state爲空時不衝突
        if not conflict(state, pos): # 回溯法的體現
            # 如果state的長度爲7,即到達了倒數第二行,也就是前7行皇后都已經找到了位置,最後一行又沒有衝突,返回最後一行的列座標
            if len(state) == num - 1:
                # 最後一行的(pos,)=最後一行的result,然後再遞歸回去求倒數第二行的result
                yield (pos,)
            else:
                for result in queens(num, state + (pos,)):
                    """
                    遞歸實現求state:
                        1. 向下遞歸
                        第一次(行): pos=0,剛開始不會進入if len(state) == num - 1,進入執行else,會執行queens(num, state + (pos, )),
                        第二次(行): 進入else,再調用queens(num, state + (pos, )),遞歸執行queens(num, state + (pos,) + (pos,))
                        第三次(行): 進入else,再調用queens(num, state + (pos,) + (pos,),遞歸執行queens(num, state + (pos,) + (pos,) + (pos,))
                        ...
                        第七次(行): 執行和上面的一樣,不過此時state的長度爲7
                        第八次(行): 執行f len(state) == num - 1:求出最後一行的列座標(pos,)
                        
                        2.向上遞歸
                        求出第八行的列座標,就可以求出第七行的(pos,),返回的是第七行和第八行的列座標((pos,) + result)
                        根據下一行的結果依次求出上一行的結果;
                        ....
                        最後求出第一行的列座標,返回整體結果
                    """
                    yield (pos,) + result

def prettyprint(solution):
    """
    進行友好展示:爲了至關表現棋盤,用X表示皇后的位置
    :param solution:
    :return:
    """
    def line(pos, length=len(solution)):
        return '.' * (pos) + 'X' + '.' * (length - pos -1)

    for pos in solution:
        print(line(pos))


if __name__ == '__main__':
    solutions = queens(8)
    for index, solution in enumerate(solutions):
        print('第%d種解決方案:' %(index + 1), solution )
        prettyprint(solution)
        print('*' * 50)

結果展示:

1種解決方案: (0, 4, 7, 5, 2, 6, 1, 3)
X.......
....X...
.......X
.....X..
..X.....
......X.
.X......
...X....
**************************************************2種解決方案: (0, 5, 7, 2, 6, 3, 1, 4)
X.......
.....X..
.......X
..X.....
......X.
...X....
.X......
....X...91種解決方案: (7, 2, 0, 5, 1, 4, 6, 3)
.......X
..X.....
X.......
.....X..
.X......
....X...
......X.
...X....
**************************************************92種解決方案: (7, 3, 0, 2, 5, 1, 6, 4)
.......X
...X....
X.......
..X.....
.....X..
.X......
......X.
....X...
**************************************************

在這裏插入圖片描述

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