1. 程式人生 > >帶你輕而易舉的學習python——八皇后問題

帶你輕而易舉的學習python——八皇后問題

首先我們來看一下這個著名的八皇后問題

八皇后問題:在8×8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。 

在這個問題提出之後人們又將它擴充套件到了n×n格的棋盤擺放n個皇后有多少種擺法

其實這是隻有在8×8出現這種問題嗎?那顯然不是嘛,只是發明國際象棋那哥們把棋盤設計成了8×8,再配合上下棋人的跳躍性思維於是乎產生了八皇后問題。如果當時把棋盤設計成了4×4,那現在可能就會叫4皇后問題了。

無論是幾個皇后  道理都是一樣的嘛。對吧

今天我們就以4×4的棋盤為例來解答這個問題

 

 我們看上邊的4×4的小棋盤,也就是八皇后棋盤的兒子哈哈

 在這裡對棋盤中的所有格子都進行了標號,這標號可不是我隨心所欲標出來的,這是根據我們眾所周知的二維陣列的行列標號所標出來的。每個小方格里都有兩位數字,第一位表示該格子所在的行標號(從0開始),第二位順其自然也就是列標號了。

仔細看這張圖的數字,你發現了什麼規律嗎?

我們把行標號定義為x,列標號定義為y。我們可以用座標的形式來表示每一個小方格。這是我們回想初高中所學過的函式知識啦  當然這個函式不是我們程式語言中的函式哈

我們在二維座標系中,可以根據一條直線上的兩個點求得該直線的斜率,即:k(斜率)=(y2-y1)/(x2-x1)  (x1,y1)與(x2,y2)為直線上的任意兩點

這裡有特殊的兩條直線:y=x(斜率為1) 與 y=-x(斜率為-1) 即k=1 所以:|y2-y1| = |x2-x1|

看上邊這個圖,回憶我們初中的數學知識是不是就一目瞭然啦

下邊是用python實現畫上圖的程式碼,感興趣的朋友可以回去試一試哈,很神奇的

 
 
# -*- coding: utf-8 -*-
import numpy                         #呼叫numpy的包
import matplotlib.pyplot as plt      #呼叫matplotlib包中的pyplot演算法
n = numpy.arange(10) 
plt.plot(n,n,color
="r")         #用紅色的線條
plt.show()

解決皇后的問題呢,在這說了這麼多斜率幹嘛呀?跑題了吧   不但沒有  而且二者有很大的聯絡 斜率為1不就是與水平面夾角45°嘛  斜率為-1不就是與水平面夾角135°嘛 上邊的圖中任意斜線上的方格連成一條線正好是45°或135°。不信我們隨便取兩個。

我們在上邊的圖中任意找兩個在任意斜線上的方格 比如我找(1,3)和(3,1)  連線呈45°角,可見|1-3|=|3-1|  我在找(3,2)和(1,0)同樣也滿足 也就是說處於同一斜線上的兩個方格同時也會滿足|y2-y1| = |x2-x1|。

有的朋友可能已經忘了什麼斜率這些概念了  都沒有關係,你只需要記住在同一斜線的兩個方格存在這種列表號差的絕對值與行標號差的絕對值相等即可。

好了,該說的都說了,上程式碼吧

# -*- coding: utf-8 -*-

num = 0
# 八皇后擺放函式  第一個引數為一維陣列 陣列的每個下標代表每個皇后所擺放的行號,對應的陣列中的數表示該皇后所擺放的列號
def eight_queen(arr,finish_line=0):
    if finish_line == len(arr):                     #如果放置皇后成功的行數與陣列中的元素個數一致(即棋盤的行數)則認為完成了一種擺法
        global num                                  #將上邊定義的num定義為全域性變數  這樣才能在後邊對其進行自加操作
        num+=1
        print("第%s種擺法:" % num)
        for i in range(8):
            print((i,arr[i]))
        return 0
    for stand in range(len(arr)):                   #對整個列進行掃描,將列標的標號賦值給陣列中對應的元素
        arr[finish_line] = stand
        flag = True
        for line in range(finish_line):
            #判斷前幾行對應的這一列有沒有皇后 或者當前列是否與之前的皇后處在同一斜線上 兩者在之一則列加一(向右邊換一列再試試)
            if arr[line] == stand or abs(arr[line]-stand) == finish_line-line:
                flag = False
        if flag==True:
            eight_queen(arr,finish_line+1)
if __name__ == '__main__':                          #主函式
    eight_queen([0]*8)
    if num != 0:
        print("一共有%s種擺法" % num)
    else:
        print("無解")
程式碼解析:執行程式碼,首先執行if __name__ == '__main__':下邊的 eight_queen([0]*8)    
找到def eight_queen(arr,finish_line=0):第一次執行時:finish_line=0,表示我先在要往棋盤的第0行放置皇后了 進行if finish_line == len(arr):
的條件判斷,陣列的長度為8 而finish_line為0 顯然不成立 跳過if分支 剛剛只說我要往第0行放皇后 那麼具體放到第一行的那個位置呢? 彆著急 我們進入到下邊的for迴圈中(這裡stand變量表示列):第一次進入for迴圈中 stand=0(第0列),此時:arr[0]=0;
也就是說第一個皇后等待放在第0行第0列的位置。在這裡定義一個flag標誌位,在後邊用來判斷剛剛待等待放置的皇后能否在第0行第0列的位置放置。
接下來走到了下邊的for迴圈 line=0 而 finish_line也為0 所以不會進入迴圈 直接執行if語句判斷標誌位 我們剛剛定義的就是True 所以當然成立了。這時成功的調取了 eight_queen(arr,finish_line+1) 表示我第一個皇后放置成功了,我需要往下一行(第1行)放皇后了

這是第二次呼叫該函式 此時 finish_line=1 要往第1行放皇后了 大部分執行與第一次完全一致 唯一不同的就是這時需要進行剛剛沒有執行的for迴圈了
我要往第1行的第0列放置皇后了 現在需要判斷在第1行之前的0列上是否有皇后 怎麼判斷呢? 我剛剛把被佔用列已經放到的arr這個陣列中了啊 只需要判斷我這次往陣列中放的列原來在陣列中有沒有不就行了嘛
於是我們判斷arr[0]==stand嗎? 我們已知arr[0]為0(第0行第0列有皇后) 所以第1行第0列不能再有皇后了 繼續執行迴圈體 判斷第1行第1列能放皇后嗎?arr[0]==stand顯然不成立 而第1行第1列與第0行第0列正好處於同一斜線 即|1-0|=1-0 所以這個格子也不能放皇后。
繼續執行迴圈體 判斷第1行第2列能放皇后嗎? 會發現if的兩個條件都不滿足 那當然可以放了 於是第二個皇后有著落啦 往後的每一個皇后都是這樣去迴圈判斷是否可以放置的。
 

程式執行結果部分截圖,可見輸出都是皇后擺放位置的座標。當然如果你想解決n皇后的問題,直接把eight_queen([0]*8)中的8改成你心中的n就Ok啦。

再看下邊這個程式:

# -*- coding: utf-8 -*-
arr2 =[[0, 0, 0, 0, 0, 0, 0, 0], #用二維陣列來模擬那個8*8的棋盤(low到爆的辦法)
[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]] num = 0 def eight_queen(arr,finish_line=0): if finish_line == len(arr): #如果放置皇后成功的行數與陣列中的元素個數一致(即棋盤的行數)則認為完成了一種擺法 for x in range(len(arr2)): for y in range(len(arr2)): arr2[x][y]=0 global num #將上邊定義的num定義為全域性變數 這樣才能在後邊對其進行自加操作 num+=1 print("第%s種擺法:" % num) for i in range(8): for x in range(len(arr2)): for y in range(len(arr2)): if i == x and arr[i]==y: arr2[x][y] = “皇” print(arr2) return 0 for stand in range(len(arr)): #對整個列進行掃描,將列標的標號賦值給陣列中對應的元素 arr[finish_line] = stand flag = True for line in range(finish_line): if arr[line] == stand or abs(arr[line]-stand) == finish_line-line: flag = False if flag==True: eight_queen(arr,finish_line+1) if __name__ == '__main__': eight_queen([None]*8) if num != 0: print("一共有%s種擺法" % num) else: print("無解")

 

看上去比剛剛好點啦,但是還是很彆扭,接下來我們引用科學計算的內容numpy來轉這個陣列

 
 
 
# -*- coding: utf-8 -*-
import numpy                                      #引用numpy包(引之前需要安裝numpy直譯器)
arr2 =[[0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0]]
num = 0
def eight_queen(arr,finish_line=0):
    if finish_line == len(arr):                     #如果放置皇后成功的行數與陣列中的元素個數一致(即棋盤的行數)則認為完成了一種擺法
        for x in range(len(arr2)):
            for y in range(len(arr2)):
                arr2[x][y]=0
        global num                                  #將上邊定義的num定義為全域性變數  這樣才能在後邊對其進行自加操作
        num+=1
        print("第%s種擺法:" % num)
        for i in range(8):
            for x in range(len(arr2)):
                for y in range(len(arr2)):
                    if i == x and arr[i]==y:
                        arr2[x][y] = ""
        arr3 = numpy.array(arr2)                    #將二維陣列轉化成array格式(就是一種格式而已,轉完了就是好看)
        print(arr3)
        return 0
    for stand in range(len(arr)):                   #對整個列進行掃描,將列標的標號賦值給陣列中對應的元素
        arr[finish_line] = stand
        flag = True
        for line in range(finish_line):
            if arr[line] == stand or abs(arr[line]-stand) == finish_line-line:
                flag = False
        if flag==True:
            eight_queen(arr,finish_line+1)
if __name__ == '__main__':
    eight_queen([None]*8)
    if num != 0:
        print("一共有%s種擺法" % num)
    else:
        print("無解")
 

這回就好看多啦是吧。
第一次寫,望讀者有意見多多指出。