1. 程式人生 > >A*演算法解決15數碼問題_Python實現

A*演算法解決15數碼問題_Python實現

1問題描述

數碼問題常被用來演示如何在狀態空間中生成動作序列。一個典型的例子是15數碼問題,它是由放在一個4×4的16宮格棋盤中的15個數碼(1-15)構成,棋盤中的一個單元是空的,它的鄰接單元中的數碼可以移到該單元中,通過這樣不斷地移動數碼來改變棋盤佈局,使棋盤從給定的初始棋局變為目標棋局(圖1)。【數字華容道】

圖1-1. 十五數碼問題

2.知識表達

    常見的知識表達有狀態空間、與/或圖、語義網、謂詞邏輯等。狀態空間是表示問題及其搜尋過程的一種方法,是人工智慧最基本的形式化方法。對於數碼問題,使用狀態空間來描述更為直觀易懂,更有助於對演算法的理解,故本文采用狀態空間來對問題進行表達。

    狀態空間的三要素:狀態、操作符、狀態空間。

(1).狀態S:十五數碼問題中,每種棋局就是一個狀態,所有棋局就是狀態集合S,其中共有16!=209227898888000個狀態。

(2).操作符F:使用最簡化4個操作:分別向上、下、左、右移動空白單元,將操作符作用到某一狀態即可從該狀態轉移到另一狀態。但值得注意的是,並不是所有狀態都可以執行這4個操作符。F = {上, 下, 左, 右}

(3).狀態空間(S, F, G),其中狀態空間圖G的一部分如圖1-2所示。

圖1-2. 十五數碼的部分狀態空間圖

2. A*演算法

2.1演算法簡介

A*演算法是BFS的一個變種,不同於BFS的是,每次選擇節點進行生成的時候,優先選擇估價函式最小的節點,把原來的BFS演算法的無啟發式的搜尋改成了啟發式的搜尋,可以有效的減少節點的搜尋個數。其估價函式f(x)= g(x)+h(x)的設計對搜尋效率的影響是至關重要的,對於十五數碼問題的估價函式中的g(x)我們選擇從初始狀態到當前狀態x的操作符個數,即搜尋樹中x狀態的深度。對於啟發函式h(x),本文使用了兩種方案:

(1).狀態x中“不在位”的數碼的個數,即當前狀態x與目標狀態不同元素的個數;

(2).曼哈頓距離,即當前狀態x與目標狀態不同元素之間對應橫縱座標差的絕對值之和。

2.2 演算法原理

從初始狀態S_0出發,分別採用不同的操作符作用於生成新的狀態x並將其加入open表中(對應到狀態空間圖中便是根節點生成新的子節點n) ,接著從open表中按照某種限制或策略選擇一個狀態x使操作符作用於x又生成了新的狀態並加入open表中(狀態空間圖中相應也產生了新的子節點),如此不斷重複直到生成目標狀態。

對於以上所述的“某種策略”,在圖搜尋過程中,若該策略是依據進行排序並選取最小的估價值,則稱該過程為A演算法。其中:

 是從初始狀態S_0經由狀態x到目標狀態S_G的代價估計

 是在狀態空間中從初始狀S_0態到狀態x的實際代價

 是從狀態x到目標狀態S_G的最佳路徑的代價

A演算法中,若對所有的x存在h(x)≤,則稱h(x)為的下限,表示某種偏於保守的估計。採用的下限h(x)為啟發函式的A演算法,稱為A*演算法,其中限制:h(x)≤h*(x)十分重要,它能保證A*演算法找到最優解。在本問題中,g(x)相對容易得到,就是從初始節點到當前節點的路徑代價,即當前節點在搜尋樹中的深度。關鍵在於啟發函式h(x)的選擇,A*演算法的搜尋效率很大程度上取決於估價函式h(x)。一般而言,滿足h(x)≤h*(x)前提下,h(x)的值越大越好,說明其攜帶的啟發性資訊越多,A*演算法搜尋時擴充套件的節點就越少,搜尋效率就越高。

傳統的BFS是選取當前節點在搜尋樹中的深度作為g(x),但沒有使用啟發函式h(x),在找到目標狀態之前盲目搜尋,生成了過多的節點,因此搜尋效率相對較低。本文分別使用不在位的元素個數和曼哈頓距離作為啟發函式h(x)。每次從open表中選取時,優先選取估價函式最小的狀態來擴充套件。

2.3 演算法流程

本演算法只考慮找到一條最優解即可,不需要找到所有可行解。

初始化兩個表為空:open表和close表

1). 將初始節點加入open表(其父節點指標為null)

2). 若open表為空,則問題無解,退出。

3). 在open表中取出f(x)最小的節點作為當前節點x,並放入close表中;

4). 判斷節點x是否為目標節點:若是,則找到問題的解,退出。

5). 若節點x不可擴充套件,則轉到第2)步;

6). 擴充套件節點x(分別按照上、下、左、右方向移動空格並且操作起作用)得到多個子節點,計算它們的估價值並配置其父節點指標指向x,挨個判斷每個子節點是否已經在open表中:

如果open表已有該子節點,比較二者的估價函式f(x)值,如果先前的f(x)大於現在新生成的子節點,則更新其為新生成的子節點,否則放棄加入,考察下一個子節點;(事實上,它們的h(x)是相同的,先出現的節點其g(x)不會大於後生成的,所以此步是沒有必要的)

如果open表中沒有該子節點且close表中也沒有,則將該子節點加入open表中。

轉到第2)步(對於open表沒有該子節點但close表中有的情況不予處理,因為如果close表中節點的f(x)小於現在新生成的子節點,那麼前者的子節點的估價函式也會小於後者子節點的估價函式,相應地也先被擴充套件,最終也會最先找到最優解,因為本文的目標是找到一條最佳路徑即可)。

【一個思考:open表是不是可以考慮用set而不是用list,因為對於先加入open set的節點,其f(x)必然不會大於後加入的節點,所以後生成的節點在加入open set的時候,直接被拒絕就可以了】

【不清楚對不對,可以先看下這篇文章