問題描述
如下面第一個圖的九宮格中,放着 1~8 的數字卡片,還有一個格子空着。與空格子相鄰的格子中的卡片可以移動到空格中。經過若干次移動,可以形成第二個圖所示的局面。
我們把第一個圖的局面記為:12345678.
把第二個圖的局面記為:123.46758
顯然是按從上到下,從左到右的順序記錄數字,空格記為句點。
本題目的任務是已知九宮的初態和終態,求最少經過多少步的移動可以到達。如果無論多少步都無法到達,則輸出-1。
輸入格式
輸入第一行包含九宮的初態,第二行包含九宮的終態。
輸出格式
輸出最少的步數,如果不存在方案,則輸出-1。
樣例輸入
12345678.
123.46758
樣例輸出
3
樣例輸入
13524678.
46758123.
樣例輸出
22
思路
這是一個很典型的八數碼問題,對於每種局面,他可以將空白向上、下、左、右四個方向移動,問題的關鍵在於向哪個方向移動會更接近目標(剪枝),我們可以根據曼哈頓距離或者不在位數來建立一個評估函數,那么我們優先趨向於處理評估函數值更小的那種方向,保留一個open表記錄已經出現的局面,避免回頭重復。
不在位數是指若對於一個格子,他的對應位置的數不是目標數,則加一
曼哈頓距離是指每個數要移動到目標位置需要移動的次數之和
Python源代碼(這里的代碼是一個在課堂上寫的八數碼的代碼,不是這個題目的代碼)
#8數碼問題
import copy
import time
class status():
# 以樹的形式記錄搜索
# status是樹的一個節點
father=None
def __init__(self,lst,evaluate,step):
self.lst=lst # 記錄當前狀態
self.evaluate=evaluate # 估價函數值
self.step=step #遞歸樹的深度
def point_father(self,f):
self.father=f # 指向父節點
def change_evaluate(self,e):
self.evaluate=e # 更新估價函數值
def compute_count_unpos(lst,b):
# 不在位數
count=0
for i in range(9):
if(lst[i]==0):
continue
if(lst[i]!=b.lst[i]):
count+=1
return count
def compute_manhadun_dis(lst,b):
# 曼哈頓距離
distance=0
for i in range(9):
if(lst[i]==0):
continue
des=0
for j in range(9):
if(lst[i]==b.lst[j]):
des=j
break
hang1=des//3
hang2=i//3
des=des%3
i=i%3
distance+=(abs(hang1-hang2)+abs(des-i))
return distance
def develop(now):
# 通過移動空白位置,拓展現在的狀態,生成移動后可能的狀態並以列表形式返回
out=[]
temp = copy.deepcopy(now.lst)
for i in range(len(temp)):
if(temp[i]==0):
pos = i
#向上
if(pos//3>0):#向上
temp[pos],temp[pos-3]=temp[pos-3],temp[pos]
out.append(temp)
temp = copy.deepcopy(now.lst)
if(pos//3<2):#向下
temp[pos],temp[pos+3]=temp[pos+3],temp[pos]
out.append(temp)
temp = copy.deepcopy(now.lst)
if(pos%3>0):#向左
temp[pos],temp[pos-1]=temp[pos-1],temp[pos]
out.append(temp)
temp = copy.deepcopy(now.lst)
if(pos%3<2):#向右
temp[pos],temp[pos+1]=temp[pos+1],temp[pos]
out.append(temp)
global generate
generate+=len(out)
return out
def judge_exist(child,table):
#判斷child的list是否在table表中存在
for i in range(len(table)):
if(table[i].lst==child):
return i
return -1
def print_father(now):
if(now.father==None):
print(now.lst,now.evaluate)
return
print_father(now.father)
print(now.lst,now.evaluate)
def main(compute_evaluate):
global expand
lst=[2,8,3,1,6,4,7,0,5] # 初始化狀態
b=status([1,2,3,8,0,4,7,6,5],0,0) # 目標狀態
a=status(lst,compute_evaluate(lst,b)+0,0) # 得到節點a
open=[a] # 初始化open表
closed=[]
flag=0
step=0 # 深度初始化為0
while(len(open)!=0):
now=open[0]
open.remove(now) #取出open表的首節點
if(now.lst==b.lst): #如果他是目標狀態,則結束
print("匹配成功!")
print_father(now)
flag=1
break
children=develop(now) #生成當前狀態的后續狀態
if(len(children)==0): #無法生成,則取下個節點
continue
for child in children:
if(judge_exist(child,open)==-1 and judge_exist(child,closed)==-1): #如果子狀態不在open表或closed表
evaluate=compute_evaluate(child,b)+now.step+1 #計算估價函數值
node=status(child,evaluate,now.step+1) #成為新狀態加入open表,深度+1
node.point_father(now) #父節點指向now
expand+=1
open.append(node)
elif(judge_exist(child,open)!=-1): #如果已經在open表
index=judge_exist(child,open) #得到子節點在open表的下標
if(now.step+1<open[index].step): #如果是沿着更短的深度到達
expand += 1
open[index].step=now.step+1 #更新子節點的估價函數和深度和父節點
open[index].point_father(now)
open[index].change_evaluate(compute_evaluate(child,b)+now.step+1)
elif(judge_exist(child,closed)!=-1): #如果已經在closed表
index = judge_exist(child, closed) #得到子節點在closed表的下標
if(now.step+1<closed[index].step): #如果是沿着更短的深度到達
expand += 1
temp=closed[index] #從closed表中移除
closed.remove(temp)
temp.step=now.step+1 #並重新加入open表中
temp.change_evaluate(compute_evaluate(child,b)+now.step+1)
temp.point_father(now)
open.append(temp)
closed.append(now) #closed表移除當前已處理節點
open.sort(key=lambda x:x.evaluate,reverse=False) #按照估價函數值排序open表,第一個元素估價函數值最高
if(flag==0): #open表為空且未到達目標節點
print("匹配失敗!")
print("------不在為數估計函數--------")
expand=0
generate=0
time1=time.time()
main(compute_count_unpos)
time2=time.time()
print("運行時間為:",time2-time1)
print("生成節點數為:",generate)
print("拓展節點數為:",expand)
print("------曼哈頓距離估價函數--------")
expand=0
generate=0
time1=time.time()
main(compute_manhadun_dis)
time2=time.time()
print("運行時間為:",time2-time1)
print("生成節點數為:",generate)
print("拓展節點數為:",expand)