1. 程式人生 > >A搜尋演算法(python)之八數碼問題

A搜尋演算法(python)之八數碼問題

##什麼是啟發式搜尋演算法

啟發式搜尋(Heuristically Search)又稱為有資訊搜尋(Informed Search),它是利用問題擁有的啟發資訊來引導搜尋,達到減少搜尋範圍、降低問題複雜度的目的,這種利用啟發資訊的搜尋過程稱為啟發式搜尋。

啟發式搜尋包括A演算法和A*演算法。 啟發式演算法的核心思想:

f(x)=g(x)+h(x)

評估函式f(x)定義為:從初始節點S0出發,約束地經過節點X到達目標節點Sg的所有路徑中最小路徑代價的估計值。 其一般形式為f(x)=g(x)+h(x),g(x)表示從初始節點S0到節點X的實際代價;h(x)表示從X到目標節點Sg的最優路徑的估計代價。 ##A演算法

1,將初始節點裝入OPEN表 2,如果OPEN表為空,則失敗,退出;否則,取出OPEN表中第一個節點,加入到CLOSE表中。 3,如果節點是目標節點,則成功,退出。 4,如果節點可擴充套件,將節點的擴充套件節點加入到OPEN表中,將OPEN表按照估價函式由小到大排列;否則跳轉第2步。 ##A演算法和A演算法的差異 A演算法是由f(x)=g(x)+h(x)決定,g(x)是這一步的代價函式,h(x)是這一步的預估函式; A演算法是f(x)=g*(x)+h*(x)這個算是決定,在A演算法的基礎上添加了約束條件,g*(x),h*(x)<=任意h(x); 以上只不過是定義,對於一個例項來說,h(x)由很多種,h(x)只是估值函式的一個集合,有各種方法h1(x)h2(x)h3(x)…,取其中任意一個方法帶入上述公式,組成評判函式,都是A演算法的實現,現在取從集合中一個函式h∗(x),使得它比集合中任意的函式都優秀,這樣的演算法叫A演算法。 也就是A

演算法是最優的A演算法,(因為估值函式最優)!

##八數碼問題

八數碼問題也稱為九宮問題。在3×3的棋盤上擺有八個棋子,每個棋子上標有1至8的某一數字,不同棋子上標的數字不相同。棋盤上還有一個空格,與空格相鄰的棋子可以移到空格中。給出一個初始狀態和一個目標狀態,求出從初始狀態轉變成目標狀態的移動棋子步數的最少值。

初始數碼 目標數碼
283 123
105 456
476 780

值得注意的是編碼過程中因為涉及到python列表的複製,所以採用了深度複製,對於python的語法還在學習當中,有興趣的同學可以自己瞭解一下。

另外如何判斷數碼是否有解?

八數碼問題的一個狀態實際上是0~9的一個排列,對於任意給定的初始狀態和目標,不一定有解,也就是說從初始狀態不一定能到達目標狀態。因為排列有奇排列和偶排列兩類,從奇排列不能轉化成偶排列或相反。 如果一個數字0~8的隨機排列871526340,用F(X)表示數字X前面比它小的數的個數,全部數字的F(X)之和為Y=∑(F(X)),如果Y為奇數則稱原數字的排列是奇排列,如果Y為偶數則稱原數字的排列是偶排列。 例如871526340這個排列的 Y=0+0+0+1+1+3+2+3+0=10 10是偶數,所以他偶排列。871625340 Y=0+0+0+1+1+2+2+3+0=9 9是奇數,所以他奇排列。 因此,可以在執行程式前檢查初始狀態和目標狀態的窘是否相同,相同則問題可解,應當能搜尋到路徑。否則無解。

廢話不多說,接下來看程式碼: 檔案A.py

# coding=utf-8
from __future__ import print_function
import copy

def showMap(array2d):
	for x in xrange(0, 3):
		for y in xrange(0, 3):
			print(array2d[x][y], end='')
		print(" ")
	print("--------")
	return;

def move(array2d, srcX, srcY, drcX, drcY):
    temp = array2d[srcX][srcY]
    array2d[srcX][srcY] = array2d[drcX][drcY]
    array2d[drcX][drcY] = temp
    return array2d;

#計算是奇數列還是偶數列
def getStatus(array2d):
	y = 0;

	for i in xrange(0, 3):
		for j in xrange(0, 3):
			for m in xrange(0, i+1):
				for n in xrange(0, j):
					if array2d[i][j] > array2d[m][n]:
						y += 1;
	return y;
#描述A演算法中的節點資料 
class Node:     
    def __init__(self, array2d, g = 0, h = 0):  
        self.array2d = array2d        #二維陣列  
        self.father = None        #父節點  
        self.g = g                #g值
        self.h = h                #h值  
  
    """
    估價公式
     """
    def setH(self, endNode):
        for x in xrange(0, 3):
        	for y in xrange(0, 3):
        		for m in xrange(0, 3):
        			for n in xrange(0, 3):
        				if self.array2d[x][y] == endNode.array2d[m][n]:
        					self.h += abs(x*y - m*n)

    
    def setG(self, g):
        self.g = g

    def setFather(self, node):
        self.father = node

    def getG(self):
    	return self.g

class A:
    """
    A 演算法 
    python 2.7 
    """
    def __init__(self, startNode, endNode):
        """ 
        startNode:  尋路起點 
        endNode:    尋路終點 
        """  
        #開放列表
        self.openList = []
        #封閉列表  
        self.closeList = []
        #起點  
        self.startNode = startNode
        #終點
        self.endNode = endNode 
        #當前處理的節點
        self.currentNode = startNode
        #最後生成的路徑
        self.pathlist = []
        #step步
        self.step = 0
        return;

    def getMinFNode(self):
        """ 
        獲得openlist中F值最小的節點 
        """  
        nodeTemp = self.openList[0]  
        for node in self.openList:  
            if node.g + node.h < nodeTemp.g + nodeTemp.h:  
                nodeTemp = node  
        return nodeTemp

    def nodeInOpenlist(self,node):
        for nodeTmp in self.openList:  
            if nodeTmp.array2d == node.array2d:  
                return True  
        return False

    def nodeInCloselist(self,node):
        for nodeTmp in self.closeList:  
            if nodeTmp.array2d == node.array2d:  
                return True  
        return False

    def endNodeInOpenList(self):  
        for nodeTmp in self.openList:  
            if nodeTmp.array2d == self.endNode.array2d:  
                return True  
        return False

    def getNodeFromOpenList(self,node):  
        for nodeTmp in self.openList:  
            if nodeTmp.array2d == node.array2d:  
                return nodeTmp  
        return None

    def searchOneNode(self,node):
        """ 
        搜尋一個節點
        """  
        #忽略封閉列表
        if self.nodeInCloselist(node):  
            return  
        #G值計算 
        gTemp = self.step

        #如果不再openList中,就加入openlist  
        if self.nodeInOpenlist(node) == False:
            node.setG(gTemp)
            #H值計算 
            node.setH(self.endNode);
            self.openList.append(node)
            node.father = self.currentNode
        #如果在openList中,判斷currentNode到當前點的G是否更小
        #如果更小,就重新計算g值,並且改變father 
        else:
            nodeTmp = self.getNodeFromOpenList(node)
            if self.currentNode.g + gTemp < nodeTmp.g:
                nodeTmp.g = self.currentNode.g + gTemp  
                nodeTmp.father = self.currentNode  
        return;

    def searchNear(self):
        """ 
        搜尋下一個可以動作的數碼
        找到0所在的位置並以此進行交換
        """ 
        flag = False
        for x in xrange(0, 3):
        	for y in xrange(0,3):
        		if self.currentNode.array2d[x][y] == 0:
        			flag = True
        			break;
        	if flag == True:
        		break;

        self.step += 1
        if x - 1 >= 0:
        	arrayTemp = move(copy.deepcopy(self.currentNode.array2d), x, y, x - 1, y)
        	self.searchOneNode(Node(arrayTemp));
        if x + 1 < 3:
        	arrayTemp = move(copy.deepcopy(self.currentNode.array2d), x, y, x + 1, y)
        	self.searchOneNode(Node(arrayTemp));
        if y - 1 >= 0:
        	arrayTemp = move(copy.deepcopy(self.currentNode.array2d), x, y, x, y - 1)
        	self.searchOneNode(Node(arrayTemp));
        if y + 1 < 3:
        	arrayTemp = move(copy.deepcopy(self.currentNode.array2d), x, y, x, y + 1)
        	self.searchOneNode(Node(arrayTemp));

        return;

    def start(self):
    	''''' 
        開始尋路 
        '''
        #根據奇數列和偶數列判斷是否有解
        startY = getStatus(self.startNode.array2d)
        endY = getStatus(self.endNode.array2d)

        if startY%2 != endY%2:
        	return False;
        #將初始節點加入開放列表
        self.startNode.setH(self.endNode);
        self.startNode.setG(self.step);
        self.openList.append(self.startNode)

        while True:
        	#獲取當前開放列表裡F值最小的節點
        	#並把它新增到封閉列表,從開發列表刪除它
        	self.currentNode = self.getMinFNode()
        	self.closeList.append(self.currentNode)
        	self.openList.remove(self.currentNode)
        	self.step = self.currentNode.getG();

        	self.searchNear();

        	#檢驗是否結束
        	if self.endNodeInOpenList():
        		nodeTmp = self.getNodeFromOpenList(self.endNode)
        		while True:
        			self.pathlist.append(nodeTmp);
        			if nodeTmp.father != None:
        				nodeTmp = nodeTmp.father
        			else:
        				return True;
        	elif len(self.openList) == 0:
        		return False;
        	elif self.step > 30:
        		return False;

        return True;

    def showPath(self):
    	for node in self.pathlist[::-1]:
    		showMap(node.array2d)
# coding=utf-8
import A

if __name__ == '__main__':

	##構建A
	a = A.A(A.Node([[2,8,3],[1,0,5],[4,7,6]]), A.Node([[1,2,3],[4,5,6],[7,8,0]]));
	print "A start:";
	##開始尋路
	if a.start():
		a.showPath();
	else:
		print "no way";

##執行結果

A start:
283 
105 
476 
--------
203 
185 
476 
--------
023 
185 
476 
--------
123 
085 
476 
--------
123 
485 
076 
--------
123 
485 
706 
--------
123 
405 
786 
--------
123 
450 
786 
--------
123 
456 
780 
--------
[Finished in 0.8s]