1. 程式人生 > >利用python求解八數碼難題

利用python求解八數碼難題

實驗目的

  • 實驗內容
    八數碼問題也稱為九宮問題。在3×3的棋盤,擺有八個棋子,每個棋子上標有1至8的某一數字,不同棋子上標的數字不相同。棋盤上還有一個空格,與空格相鄰的棋子可以移到空格中。要求解決的問題是:給出一個初始狀態和一個目標狀態,找出一種從初始狀態轉變成目標狀態的移動棋子步數最少的移動步驟。
  • 實驗要求
    分別利用寬度優先搜尋有序搜尋演算法求解八數碼難題,給出搜尋樹,並給出從初始節點到目標節點的路徑。

實驗裝置及軟體環境

1. 電腦配置:

(1)處理器 : Intel i5-4210U CPU @ 1.70GHz, 2.40GHz
(2)安裝記憶體: 8.00GB
(3)作業系統: Windows 10
(4)程式語言: Python
(5)軟體環境: python 3.5 、numpy、matplotlib、scipy、Axure 7.0
(6)IDE : PyCharm 5.0.1

實驗方法

  • 演算法描述

    (1) 寬度優先搜尋
    如果搜尋是以接近起始節點的程度依次擴充套件節點的,那麼這種搜尋就叫做寬度優先搜尋。這種搜尋是逐層進行的,在對下一層的任一節點進行搜尋之前,必須搜尋完本層的所有節點。
    (2) 有序搜尋
    f(n) 表示節點n的估價函式值,估算節點希望程度的量度。本次實驗選擇的f(n)的函式形式為:

    f(n)=g(n)+h(n)
    其中, g(n)為初始節點到當前節點的路徑長度(深度), h(n)為當前節點“不在位”的將牌數。
    有序搜尋(ordered search),即最好優先搜尋, 選擇Open表上具有最小f值的節點作為下一個要擴充套件的節點。
  • 流程圖
    (1) 寬度優先搜尋
Created with Raphaël 2.1.0開始把起始節點S 放入OPEN表Open表為空?失敗把第一個節點(n)從Open表移至Closed表擴充套件n,把n的後繼節點放入Open表的末端,提供返回節點n的指標是否有後繼節點為目標節點?成功並結束yesnoyesno
(2) 有序搜尋
Created with Raphaël 2.1.0開始把起始節點S放入OPEN表,計算估計函式f(s)Open表為空?失敗並退出選取Open表中f值最小的節點i移至Closed表i為目標節點?成功並結束擴充套件i,得後繼節點j,計算f(j),提供返回節點i的指標,利用f(j)重排Open表
yesnoyesno
  • 程式程式碼 (python)
    (1) 寬度優先搜尋
__author__ = 'ysc'
import numpy as np

class State:
    def __init__(self, state, directionFlag=None, parent=None):
        self.state = state        
        # state is a ndarray with a shape(3,3) to storage the state
        self.direction = ['up', 'down', 'right', 'left']
        if directionFlag:
            self.direction.remove(directionFlag)  
       # record the possible directions to generate the sub-states
        self.parent = parent
        self.symbol = ' '

    def getDirection(self):
        return self.direction

    def showInfo(self):
        for i in range(3):
            for j in range(3):
                print(self.state[i, j], end='  ')
            print("\n")
        print('->')
        return

    def getEmptyPos(self):
        postion = np.where(self.state == self.symbol)
        return postion

    def generateSubStates(self):
        if not self.direction:
            return []
        subStates = []
        boarder = len(self.state) - 1         
        # the maximum of the x,y
        row, col = self.getEmptyPos()
        if 'left' in self.direction and col > 0:
        #it can move to left 
            s = self.state.copy()
            temp = s.copy()
            s[row, col] = s[row, col-1]
            s[row, col-1] = temp[row, col]
            news = State(s, directionFlag='right', parent=self)
            subStates.append(news)
        if 'up' in self.direction and row > 0:    
        #it can move to upper place
            s = self.state.copy()
            temp = s.copy()
            s[row, col] = s[row-1, col]
            s[row-1, col] = temp[row, col]
            news = State(s, directionFlag='down', parent=self)
            subStates.append(news)
        if 'down' in self.direction and row < boarder:        #it can move to down place
            s = self.state.copy()
            temp = s.copy()
            s[row, col] = s[row+1, col]
            s[row+1, col] = temp[row, col]
            news = State(s, directionFlag='up', parent=self)
            subStates.append(news)
        if self.direction.count('right') and col < boarder:    #it can move to right place
            s = self.state.copy()
            temp = s.copy()
            s[row, col] = s[row, col+1]
            s[row, col+1] = temp[row, col]
            news = State(s, directionFlag='left', parent=self)
            subStates.append(news)
        return subStates

    def solve(self):
        # generate a empty openTable
        openTable = []                  
       # generate a empty closeTable
        closeTable = []      
        # append the origin state to the openTable         
        openTable.append(self)    
        steps = 1
        # start the loop
        while len(openTable) > 0:      
            n = openTable.pop(0)
            closeTable.append(n)
            subStates = n.generateSubStates()
            path = []
            for s in subStates:
                if (s.state == s.answer).all():
                    while s.parent and s.parent != originState:
                        path.append(s.parent)
                        s = s.parent
                    path.reverse()
                    return path, steps+1
            openTable.extend(subStates)
            steps += 1
        else:
            return None, None

if __name__ == '__main__':
    # the symbol representing the empty place
    # you can change the symbol at here
    symbolOfEmpty = ' '       

    State.symbol = symbolOfEmpty     
    # set the origin state of the puzzle
    originState = State(np.array([[2, 8, 3], [1, 6 , 4], [7, symbolOfEmpty, 5]])) 
    # and set the right answer in terms of the origin
    State.answer = np.array([[1, 2, 3], [8, State.symbol, 4], [7, 6, 5]])        
    s1 = State(state=originState.state)
    path, steps = s1.solve()
    if path:    # if find the solution
        for node in path:    
                # print the path from the origin to final state      
                node.showInfo()
        print(State.answer)
        print("Total steps is %d" % steps)

(2)有序搜尋演算法

__author__ = 'ysc'
import numpy as np

class State:
    def __init__(self, state, directionFlag=None, parent=None, depth=1):
        self.state = state        
        # state is a ndarray with a shape(3,3) to storage the state
        self.direction = ['up', 'down', 'right', 'left']
        if directionFlag:
            self.direction.remove(directionFlag)  
            # record the possible directions to generate the sub-states
        self.parent = parent
        self.symbol = ' '
        self.answer = np.array([[1, 2, 3], [8, State.symbol, 4], [7, 6, 5]])
        self.depth = depth
        # calculate the num of elements which are not in the proper position
        num = 0
        for i in range(len(state)):
            for j in range(len(state)):
                if self.state[i, j] != ' 'and self.state[i, j] != self.answer[i, j]:
                    num += 1
        self.cost = num + self.depth

    def getDirection(self):
        return self.direction

    def showInfo(self):
        for i in range(3):
            for j in range(3):
                print(self.state[i, j], end='  ')
            print("\n")
        print('->')
        return

    def getEmptyPos(self):
        postion = np.where(self.state == self.symbol)
        return postion
    def generateSubStates(self):
        if not self.direction:
            return []
        subStates = []
        # the maximum of the x,y
        row, col = self.getEmptyPos()
        if 'left' in self.direction and col > 0:    
        #it can move to left place
            s = self.state.copy()
            temp = s.copy()
            s[row, col] = s[row, col-1]
            s[row, col-1] = temp[row, col]
            news = State(s, directionFlag='right', parent=self, depth=self.depth+1)
            subStates.append(news)
        if 'up' in self.direction and row > 0:    
        #it can move to upper place
            s = self.state.copy()
            temp = s.copy()
            s[row, col] = s[row-1, col]
            s[row-1, col] = temp[row, col]
            news = State(s, directionFlag='down', parent=self, depth=self.depth+1)
            subStates.append(news)
        if 'down' in self.direction and row < boarder:
            #it can move to down place   
            s = self.state.copy()
            temp = s.copy()
            s[row, col] = s[row+1, col]
            s[row+1, col] = temp[row, col]
            news = State(s, directionFlag='up', parent=self, depth=self.depth+1)
            subStates.append(news)
        if self.direction.count('right') and col < boarder:
            #it can move to right place    
            s = self.state.copy()
            temp = s.copy()
            s[row, col] = s[row, col+1]
            s[row, col+1] = temp[row, col]
            news = State(s, directionFlag='left', parent=self, depth=self.depth+1)
            subStates.append(news)
        return subStates
    def solve(self):
        # generate a empty openTable
        openTable = []
        # generate a empty closeTable                 
        closeTable = []
        # append the origin state to the openTable                
        openTable.append(self)
        # denote the steps it travels         
        steps = 0                    
        while len(openTable) > 0:     # start the loop
            n = openTable.pop(0)
            closeTable.append(n)
            subStates = n.generateSubStates()
            path = []
            for s in subStates:
                if (s.state == s.answer).all():
                    while s.parent and s.parent != originState:
                        path.append(s.parent)
                        s = s.parent
                    path.reverse()
                    return path, steps+1
            openTable.extend(subStates)
            # sort the openTable in terms of the cost
            openTable.sort(key=lambda x: x.cost)  
            steps += 1
        else:
            return None, None
if __name__ == '__main__':
    # the symbol representing the empty place
    symbolOfEmpty = ' '
    # you can change the symbol at here              
    State.symbol = symbolOfEmpty
    # set the origin state of the puzzle    
    originState = State(np.array([[2, 8, 3], [1, 6 , 4], [7, symbolOfEmpty, 5]]))  
    State.answer = np.array([[1, 2, 3], [8, State.symbol, 4], [7, 6, 5]])       
    s1 = State(state=originState.state)
    path, steps = s1.solve()
    if path:                        # if find the solution
        for node in path:
                # print the path from the origin to final state         
                node.showInfo()
        print(State.answer)
        print("Total steps is %d" % steps)

實驗結果

繪圖軟體為:Axure 7.0

  • 搜尋樹
    (1)DFS
    寬度優先搜尋樹
    (2) 有序搜尋
    有序搜尋搜尋樹
  • 搜尋路徑
    DFS及有序搜尋的搜尋路徑

實驗分析

  • 結果分析
    (1) 寬度優先搜尋
    由實驗結果可知,寬度優先搜尋擴充套件節點個數為4,生成節點個數為26。
    (擴充套件節點:路徑中的節點數-1;生成節點:搜尋樹中節點數目-1)
    (2) 有序搜尋
    由實驗結果可知,有序搜尋擴充套件節點個數為4,生成節點個數為12。
    (擴充套件節點:路徑中的節點數-1;生成節點:搜尋樹中節點數目-1)
    2. 方法特點
    (1) 寬度優先搜尋
  • 寬度優先搜尋又稱廣度優先搜尋,“廣度”一詞形象地揭示了這種搜尋是逐層進行的:在對下一層的任一節點進行搜尋之前,必須搜尋完本層的所有節點。
  • 不需要重排Open表
  • 一般只適用於求解比較簡單的問題。
  • 若問題有解,寬度優先搜尋一定可以求得,並且求出的是最優解。
    (2) 有序搜尋
    有序搜尋利用到啟發式資訊,對Open表中元素進行了重排,選擇最有希望的節點加以擴充套件,搜尋效率較盲目搜尋大為提高。
    3. 區別
    寬度優先搜尋屬於盲目搜尋,沒有利用到啟發資訊,故效率較低;而有序搜尋利用到節點本身的特性,將其作為啟發式資訊,對Open表進行重排,每一次選擇都是最優的,具有貪心性質,搜尋效率大為提高。

結論

綜上所述,可以明顯看出寬度優先搜尋與有序搜尋的效率差異。這啟示我們在解決生活問題時,不僅僅是需要找到一個通用的(general)演算法框架,因為雖然可以求出解,但是效率很低,我們更需要根據實際問題具體分析,通過問題本身,提煉出更有效的啟發式資訊,這樣才能提高解的效率。