爬山法求解八皇后問題的全部解法

程序的概要設計思想

爬山算法是一種局部貪婪算法,每次更新一次狀態,都對相鄰狀態的衝突狀態進行排序,選擇最好的相鄰狀態進行更新,因此易陷入局部極值,從而無法得到最優解。

初始狀態

爲了方便對狀態序列進行操作,爬山法程序採用一維數組x[i]進行編碼,只是x[i]的範圍變成0~7。一維數組中各元素互不相同。每個編碼元素由隨機函數產生。

衝突函數

衝突函數與遺傳算法中的適應函數類似,只是在本程序中修改爲終止條件爲0。

尋找鄰居狀態

每更新一次狀態,即每挪動一個皇后,都有56個鄰居狀態。計算這些新鄰居狀態的衝突值,選擇衝突值最小的鄰居狀態作爲下一個狀態。如果本次的初始狀態可以由爬山法尋找到最優解,那麼爬山法的收斂速度是很快的,因此尋找新鄰居的次數如果大於100次,可以認爲在這個初始狀態下,爬山法尋找不到解。

尋找全部解集

解決八皇后問題的全部解集,在爬山法的程序中可以實現。多次進行爬山法求解,然後進行答案去重操作。隨着爬山法求解次數的增多,解集中的解答數量上升。當爬山法求解次數大於800次時,解集中解答數量不在變化,爲92,因此可以認爲爬山法求解八皇后問題的解集數量收斂於92。
最終得到的結果爲,八皇后問題有92個解。
注:
這裏求得的92並非嚴格意義上的完全不同的解,即如果一對解是對稱的,那麼在本程序中認爲它們是兩個解。

N·沃思在他的名著《算法+數據結構=程序》一書中給出了求解八皇后問題全部解的Pascal源程序。沃思同時指出,該程序不能識別對稱的解,雖然一切可能的解有92個,但是隻有12個真正不同的解。

這裏給出求出全部不同的八皇后問題的解的C語言程序的刊登在淮南師範學院學報上的論文。有點不應該的是,這篇論文上有個無關痛癢的錯別字。
尹星雲. 八皇后問題的全部12個不同的解[J]. 淮南師範學院學報, 1997(1):72-74.

此外,補充上次遺傳算法解決八皇后問題,因爲我採用8列單算子的編碼表示個體,本來以爲遺傳算法雖然全局收斂性比爬山法稍好,但其效率不高且性能不穩定,不大可能在有限的時間內求解出所有92種解。但這篇刊登在華中科技大學學報上的論文采用3種基本算子複合表示一個個體的方式大大提高了計算效率,節約了片內資源,並且有嚴謹的數學建模過程和嚴密的結論證明。
周康, 魏傳佳, 劉朔, et al. 八皇后問題所有解的模擬DNA算法[J]. 華中科技大學學報(自然科學版), 2009(6).

程序主要函數的作用

初始化皇后狀態,每行只有一個皇后:
def initiate(status)

定義衝突函數,主要排查對角線上有多個皇后的情況,返回衝突的皇后對數:
def conflict

定義相鄰元素:
def neighbour(status)t(status)

爬山法函數:
def climbing(status)

八皇后問題求解函數:
def qeen():

輸入異常處理
def default():

運行結果截圖

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

Python源代碼

import random
from time import sleep
import matplotlib.pyplot

#初始化皇后狀態,每行只有一個皇后
def initiate(status):
    while len(status)<8:
        r=random.randint(0,7)
        if not (r in status):
            status.append(r)
    return status

#定義衝突函數,主要排查對角線上有多個皇后的情況,返回衝突的皇后對數
def conflict(status):
    n=0
    for i in range(8):
        for j in range(i+1,8):
            if status[i]==status[j]:
                n += 1
            if status[j]-status[i]==i-j or status[j]-status[i]==j-i:
                n += 1
    return  n

#定義相鄰元素
def neighbour(status):
    next = {}
    for i in range(8):
        for j in range(8):
            if status[i] == j:
                continue
            copy = list(status)
            copy[i] = j
            next[(i, j)] = conflict(copy)
    return next

#爬山法函數
def climbing(status):
    #當前互相沖突的皇后對數
    conflictnow=conflict(status)

    #最佳後繼集合
    ans=[]

    #尋找最佳後繼
    next=neighbour(status)
    for key,value in next.items():
        if value < conflictnow:
            conflictnow=value
    for key,value in next.items():
        if value == conflictnow:
            ans.append(key)

    #若後繼元素大於一個,則隨機選擇一個
    if len(ans)>0:
        rnd=random.randint(0,len(ans)-1)
        i, j = ans[rnd][0], ans[rnd][1]
        status[i]=j
    return status

def qeen():
    #若找不到解,循環的最大次數
    max=100
    total=0
    status=[]
    status=initiate(status)
    #print("The initial status is {}".format(status))
    climbing(status)
    while conflict(status)>0:
        status=climbing(status)
        total+=1
        if total==max:
            #print("Climbinghill algorithm cannot find the answer in {} times".format(max))
            return []
    if total < max:
        #可行解的可視化
        '''print("The answer is {} which is found in {} times".format(status,total))
        x=[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5]
        y=[status.index(0)+0.5,status.index(1)+0.5,status.index(2)+0.5,status.index(3)+0.5,status.index(4)+0.5,status.index(5)+0.5,status.index(6)+0.5,status.index(7)+0.5]
        matplotlib.pyplot.title("Using ClimbingHill Algorithm to Solve 8-Queens' Problem")
        matplotlib.pyplot.axis([0,8,0,8])
        matplotlib.pyplot.grid()
        matplotlib.pyplot.plot(x, y, '*')
        matplotlib.pyplot.show()'''
        return status

#輸入異常處理
def default():
    try:
        global tests
        tests=eval(input("Please Enter Testing Times(Enter 0 To Complete This Program):"))
    except:
        print("Please Enter integer As Testing Times!")
        return False
    return True

#多次重複求解,尋找不同的解集
while True:
    t, failed=0,0
    solve=[]
    #測試次數
    try:
        tests=eval(input("Enter Testing Times(Enter 0 To Complete This Program):"))
    except:
        print("Please Enter integer As Testing Times!")
        boolean=False
        while boolean!=True:
            boolean=default()
    if tests==0:
        break
    #去掉重複的解,保留不同的解
    for i in range(tests):
        status = qeen()
        if status==[]:
            failed+=1
            continue
        elif not(status in solve):
            solve.append(status)
            t+=1
        else:
            t+=1
    print("Climbinghill algorithm failed times:{}".format(failed))
    print("Climbinghill algorithm succeeded times:{}".format(t))
    print("Climbinghill algorithm's succeeded rate:{:.2f}%".format((t-failed)/t*100))
    print("Different solutions' number:{}".format(len(solve)))
    #選擇是否輸出全部不同的解集
    ifprint=input("Do you wanna print all the different solutions? Y(y)/N(n)")
    while ifprint!='Y' and ifprint!="N" and ifprint!='y' and ifprint!='n':
        ifprint = input("Please Enter Y/y or N/n :")
    if ifprint=='y' or ifprint=='Y':
        s,t=0,0
        for i in solve:
            s+=1
            if t==5:
                print("{:3}:{}".format(s,i))
                t=0
            else:
                t+=1
                print("{:2}:{}".format(s,i),end="")
    print("")
    print("----------------Testing times:{}----------------".format(tests))
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章