问题描述
如下面第一个图的九宫格中,放着 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
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
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)