算法思考-以八皇后問題爲例 python

算法學習原則

第一:問題抽象成可能的算法方案
知道的算法越多,對基礎的算法理解越好,就越能將問題合理的抽象成具體算法
第二:算法方案的選擇

  • 效率,穩定性,甚至工作量都是常常需要考慮的原因
  • 有時候,也需要做一些實驗。需要有很強的編程能力
  • 對算法的理解越深刻,可以節省實驗式錯的時間
    第三:實現這個方案
  • 方法儘可能複用,有很強的的擴展性
  • 方便統一管理與更新

碰到一個現實問題應該如何去做

step1:理解問題,寫出輸入與輸出
step2:描述問題的處理規則與約束
step3:描述問題的結束條件與程序迭代方式
step4:以上程序做好之後,提供一個樣例輸入,進行簡單手算 明白以上三條規則
step5:以上工作做好之後,先用程序實現規則,再做程序的嵌套
step6:進行優化

示例 八皇后問題
國際象棋中的皇后比中國象棋裏的大車還厲害,皇后能橫向,縱向和斜向移動,在這三條線上的其他棋子都可以被喫掉。所謂八皇后問題就是:將八位皇后放在一張8x8的棋盤上,使得每位皇后都無法喫掉別的皇后,(即任意兩個皇后都不在同一條橫線,豎線和斜線上),問一共有多少種擺法。此問題是在1848年由棋手馬克思·貝瑟爾提出的,後面陸續有包括高斯等大數學家們給出自己的思考和解法,所以此問題不只是有年頭了,簡直比82年的拉菲還有年頭,我們今天不妨嚐嚐這老酒。
我們先舉例來理解一下這個問題的場景到底是什麼樣子的,下面的綠色格子是一個皇后在棋盤上的“封鎖範圍”,其他的皇后不能放置在這些綠格子中:
我們先舉例來理解一下這個問題的場景到底是什麼樣子的,下面的綠色格子是一個皇后在棋盤上的“封鎖範圍”,其他的皇后不能放置在這些綠格子中:
在這裏插入圖片描述
我們再放入一個皇后,看一下兩個皇后的“封鎖範圍”(綠格子不能放):

在這裏插入圖片描述
如此繼續下去,能安放下一位皇后的位置越來越少,那麼我們最終如何能安放完這8位皇后呢?
首先我們看一下特別暴力的方法:從8x8的格子裏選8個格子,放皇后,然後測試是否滿足條件,若滿足則結果加1,否則換8個格子繼續試。很顯然,64選8,並不是個小數字,十億級別的次數,夠暴力。如果換成圍棋的棋盤,畫面就會太美而不敢算。
稍加分析,我們可以得到另一個不那麼暴力的方法:顯然,每行每列最多隻能有一位皇后,如果基於這個事實再進行暴力破解,那結果會好很多。安排皇后時,第一行有8種選法,一旦第一行選定,假設選爲(1,i),那麼第二行只能選(2,j),其中,j不等於i,所以有7種選法。以此類推,需要窮舉的情況有8!=40320種,比十億級別的小很多了。
這看起來已經不錯了,但嘗試的次數還是隨着問題規模按階乘水平提高的,我們仍然不滿意,所以,“遞歸回溯”的思想就被提出了,專治這種問題。
所謂遞歸就是:針對程序後續迭代具有同樣的邏輯而調用自身的情況
回溯:爲了求得問題的解,先選擇某一種可能的情況向前探索,在探索過程中,一旦發現原來的選擇是錯誤的,就退回一步重新選擇,繼續向前探索,如此反覆進行,直至得到解或 證明無解

針對上述問題,大致有了清晰的認識,接下來我們按照上述6個步驟將這個程序寫出來
step1:理解問題,寫出輸入與輸出
input:輸入N,這裏爲8,表示程序是在8*8的網格中進行運算
output:輸出滿足規則的皇后的擺放位置,如一種擺放位置爲:

[7, 3, 0, 2, 5, 1, 6, 4] 代表行數 
[0, 1, 2, 3, 4, 5, 6, 7] 代表列
<-><-><Q><-><-><-><-><->	
<-><-><-><-><-><Q><-><->	
<-><-><-><Q><-><-><-><->	
<-><Q><-><-><-><-><-><->	
<-><-><-><-><-><-><-><Q>	
<-><-><-><-><Q><-><-><->	
<-><-><-><-><-><-><Q><->	
<Q><-><-><-><-><-><-><->

step2:描述問題的處理規則與約束
由上面的問題描述可以描述擺放的規則

  • 不能和先前放置的皇后處在同一行/列,或是在一條斜線上(左斜線或右斜線)
  • 先前:表示已經在網格中所有的位置

step3:描述問題的結束條件與程序迭代方式
結束條件:
當走完網格的最後一行或最後一列,對滿足規則的放置方式進行輸出
程序迭代方式:
由於選擇位置的方式,每一行或每一列具有相同的邏輯,因此採用遞歸的方式+for循環
如果放置新皇后該行或該列都沒有位置滿足新的皇后,則返回上一步,重新選擇上一列或行的位置後,再進行放置新皇后
step4:以上程序做好之後,提供一個樣例輸入,進行簡單手算 明白以上三條規則
爲了能夠有更直觀的認識,我們以簡單的4皇后爲例
現在我們把第一個皇后放在第一個格子,被塗黑的地方是不能放皇后的:
在這裏插入圖片描述
第二行的皇后只能放在第三格或第四格,比如我們放在第三格:
在這裏插入圖片描述
這樣一來前面兩位皇后已經把第三行全部鎖死了,第三位皇后無論放在第三行的哪裏都難逃被喫掉的厄運。於是在第一個皇后位於第一格,第二個皇后位於第三格的情況下此問題無解。所以我們只能返回上一步,來給2號皇后換個位置:
在這裏插入圖片描述
此時,第三個皇后只有一個位置可選。當第三個皇后佔據第三行藍色空位時,第四行皇后無路可走,於是發生錯誤,則返回上層調整3號皇后,而3號皇后也別無可去,繼續返回上層調整2號皇后,而2號皇后已然無路可去,則再繼續返回上層調整1號皇后,於是1號皇后往後移一格位置如下,再繼續往下安排:
在這裏插入圖片描述
step5:以上工作做好之後,先用程序實現規則,再做程序的嵌套
放置規則的實現,在程序運行的過程中,我們只存儲行,列數順序固定

def attack(row,col):
    """
    測試在(row,col)下上的皇后是否遭到攻擊
    :param row: 新皇后的行
    :param col: 新皇后的列
    :return: 若遭受到攻擊,則返回1,否則返回0
    """
    global queen
    i = 0
    atk = 0
    offset_row = offset_col = 0
    while atk != 1 and i < col:
        offset_col = abs(i - col) #左斜線或右斜線
        offset_row = abs(queen[i] - row)
        #判斷兩皇后是否在同一行或同一對角線
        if queen[i] == row or offset_row == offset_col:
            atk = 1
        i += 1
    return atk

因爲是記錄的行數,因此只需要用來判斷行數是否相等
針對斜線:利用斜線上的座標減去某一座標值,差值絕對值相等進行判斷
while的循環表示要和新座標要和先前所有的座標進行判斷
主程序:

def decide_position(value):
    for i in range(EIGHT):
        if attack(i,value) != 1:
            queen[value] = i #壓入的是行數據
            if value == EIGHT-1:  # 網格已經遍歷完
                print_table()
            else:# 有數據之後進行下一個位置的選定
                decide_position(value+1) #如果成功就直接輸出一個數據
        #後面如果進行不下去,說明上一個位置選的不好,重新對queen數組進行賦值

i表示行,value表示列
如果判斷條件不通過,在就返回上一級,一直遍歷完整個網格

輸出程序

def print_table():
    global number
    x = y = 0
    number += 1
    print('')
    print('八皇后問題的第%d組解' % number)
    print(queen)
    for x in range(EIGHT):
        for y in range(EIGHT):
            if x == queen[y]:  #按行輸出
                print('<Q>',end='')
            else:
                print('<->',end='')
        print('\t')

完整運行程序:

global number
global queen
number = 0 #計算總共有幾組解
EIGHT = 8
queen = [None] * EIGHT
# queen = queen_stack(EIGHT)
#決定皇后存放的位置
#輸出所有可能的結果
def print_table():
    global number
    x = y = 0
    number += 1
    print('')
    print('八皇后問題的第%d組解' % number)
    print(queen)
    for x in range(EIGHT):
        for y in range(EIGHT):
            if x == queen[y]:  #按行輸出
                print('<Q>',end='')
            else:
                print('<->',end='')
        print('\t')
    # input('\n...按下任意鍵繼續')

def attack(row,col):
    """
    測試在(row,col)下上的皇后是否遭到攻擊
    :param row: 新皇后的行
    :param col: 新皇后的列
    :return: 若遭受到攻擊,則返回1,否則返回0
    """
    global queen
    i = 0
    atk = 0
    offset_row = offset_col = 0
    while atk != 1 and i < col:
        offset_col = abs(i - col) #左斜線或右斜線
        offset_row = abs(queen[i] - row)
        #判斷兩皇后是否在同一行或同一對角線
        if queen[i] == row or offset_row == offset_col:
            atk = 1
        i += 1
    return atk
def decide_position(value):
    for i in range(EIGHT):
        if attack(i,value) != 1:
            queen[value] = i #壓入的是行數據
            if value == EIGHT-1:  # 網格已經遍歷完
                print_table()
            else:# 有數據之後進行下一個位置的選定
                decide_position(value+1) #如果成功就直接輸出一個數據
        #後面如果進行不下去,說明上一個位置選的不好,重新對queen數組進行賦值
decide_position(0)

step6:進行優化
其實也談不上優化,可以對主程序更改一下寫法,方便理解
新主程序

def decide_position(value):
    if value == EIGHT:  # 網格已經遍歷完
        print_table()
        return
    for i in range(EIGHT):
        if attack(i,value) != 1:
            queen[value] = i #壓入的是行數據
            # 有數據之後進行下一個位置的選定
            decide_position(value+1) #如果成功就直接輸出一個數據
        #後面如果進行不下去,說明第一個位置選的不好,重新對
        #queen數組進行賦值

問題上的描述參考鏈接:
https://www.jianshu.com/p/65c8c60b83b8

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