回溯法解決N皇后問題-Python實現(全排列+剪枝)

同系列算法問題

貪心算法解決活動安排-Python實現(排序+貪心選擇)


N皇后問題

問題

問題概述

分析問題

解決問題

編程

編程流程以及數據類型選擇

發現問題以及解決

最終實現

總結

程序缺陷以及完善

解題心路歷程


問題

在n*n格的棋盤撒上放置彼此不受攻擊的n個皇后。按照國際象棋的規矩,皇后可以攻擊與之處在同一行或者同一列或者同一斜線上的棋子。N皇后問題等價於在n * n格的棋盤上放置n個皇后,任何2個皇后不放在同一行同一列同一斜線上。


問題概述

可以將n*n的棋盤看成一個n*n的表格,放置皇后Q,且需要滿足兩個條件

  • 條件1:同行同列不能放置兩個或大於兩個皇后
  • 條件2:皇后的斜線上不能存在皇后
1 2 ... n
2      
...      
n      

 

例如:

  • 4皇后的2種擺法(一種顏色表示一種擺法):

N皇后也是類似的處理方式


分析問題

通過以上的問題概述,可以知道。N皇后的擺法可以通過列表進行將問題抽離出來

例如4皇后問題:

可以定義一個列表嵌套子列表,第i個子列表中表示第i種擺法,那麼第1個子列表,即第1種擺法,應該爲[1,1,1,1]

不考慮限制條件:

那麼問題模型就是[[1,1,1,1],...],即代表子列表[_,_,_,_]有多少種存放可能,列表又存放子列表,那麼4皇后的列表長度爲4*4*4*4=256

N皇后的列表長度爲n*n*n*n;

首先條件有2個,即上面的條件1和條件2

如何不考慮限制條件的可能再加上這兩個條件的限制,那麼就是答案了

考慮限制條件-條件1

首先考慮條件1:同行同列不能放置兩個或大於兩個皇后

子列表表示的是皇后的擺放位置,例如[1,2,3,4]:

那麼列表的第1個元素表示的是皇后擺放在棋盤的第一行第一列,

第2個元素表示的是皇后擺放在棋盤的第二行第二列,依次類推

逆推導:行值是列表的第幾個元素,列值是列表元素的值

那麼,可以知道,當列表中出現兩個或者兩個以上的相同元素時,即不滿足條件1

爲了提高算法效率,我們可以將不考慮限制條件和考慮條件1相結合,那麼就是全排列算法

例如對於列表[1,2,3,4],全排列爲[1,2,3,4],[1,2,4,3]...一共有4!=24種

考慮限制條件-條件2

條件2:皇后的斜線上不能存在皇后

斜線在表格中表示的是斜率爲+1的直線,例如以下:

滿足條件2,可以將滿足條件1問題的所有解空間,進行條件限制,可以通過函數實現,這個函數即是剪枝函數

對於滿足條件1問題的所有解空間,模型是[_,_,_,_]

元素的位置是行值,元素的值是列值,例如[1,2,3,4],,即如下表示

從以上模型可得,

i,j表示行值,a[i],a[j]表示列值

  • |a[i]-a[j]| = |i-j|,即不滿足條件2
  • |a[i]-a[j]| != |i-j|,即滿足條件2

解決問題

  • 全排列算法的實現

使用遞歸和回溯,注意遞歸體前後語句的意義

  • 剪枝函數的實現

函數範圍

0<i<len(皇后的個數)

i+1<j<len(皇后的個數)

函數邏輯

通過兩層for循環實現,通過定義常量,並在語句塊中自增,判斷語句塊是否被執行,實現對解空間的篩選


編程


編程流程以及數據類型選擇

結合以上分析,有以下步驟:

  • 首先定義一個列表,爲素材列表[1,2,3,..,n]
  • 定義一個列表,存儲全排列的結果;對素材列表進行全排列,並存入該列表中,爲全排列列表
  • 定義一個列表,用於存儲滿足條件2的列表,爲結果列表
  • 根據結果列表,進行格式打印

發現問題以及解決

全排列算法的實現,可以通過問題抽象小化,檢查邏輯是否正確。


最終實現

程序代碼:

#name:queens
#author:zhj1121
#time:2019.11.16
#全排列函數
per_result = []
def per(lst,s,e):
    if s == e:
        per_result.append(list(lst))
    else:
        for i in range(s,e):
            lst[i],lst[s] = lst[s],lst[i]#試探
            per(lst,s+1,e)#遞歸
            lst[i],lst[s] = lst[s],lst[i]#回溯
#剪枝函數
#args:[1,2,3,4]
#return true or false
def shear(lst):
    result = 0
    for i in range(len(lst)):
        for j in range(i+1,len(lst)):
            if(abs(lst[j] - lst[i]) == abs(j-i)):
                result += 1
    if(result > 0):
        return True
    else:
        return False
#格式打印函數
def stamp(st):
    for i in st:
        for j in range(len(i)):
            a = ("."*(i[j]-1)+"#"+"."*(len(i)-i[j]))
            print(a,"\t","第{}個皇后放在棋盤的第{}列".format(j+1,i[j]))
        print(" ")#負責空行
num = eval(input("請輸入皇后的個數:"))
lst = [i+1 for i in range(num)]
per(lst,0,num)
queen_lst = []
for i in per_result:
    if(shear(i) == False):
        queen_lst.append(i)
stamp(queen_lst)
print("共{:d}種可能".format(len(queen_lst)))

運行結果截圖:

4皇后

8皇后


總結


程序缺陷以及完善

回溯法解決N皇后問題並不是一個好的算法,之所以這麼說,是因爲回溯法的使用,類似於窮舉法,再加剪枝函數,雖然運行的最終結果是正確的,但其時間複雜度並沒有質的改變。

在往後的學習中,會進行嘗試使用其它的方式解決問題。


解題心路歷程

大致如上,不再概述。


本篇已完成,如有更改,會在此列出

 

 

 

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