奇数数码与8数码问题不相同,就以3数码问题为例:
问题描述
在2*2的各自中有1-3的3个数和一个空格空格随机摆放在其中的各自中,如图,要求从初始状态图到目标状态读的求解。
03 02
21 13
初始状态 目标状态
其中0表示空
对问题分析
把三数码的初始状态从上至下,从左至右拍成一排,如下所示:
03
21 ——>0321
考虑到空格与相邻位置的交换有一下两种情况:
1)左右交换
2)上下交换
对于1)来说交换不会改变排序的逆序数。2)相党羽排序中向前或者向后跳了两个数字也不会改变这个序列的逆序数。如果3数码问题优解当且仅当初始状态与目标状态排列的逆序数同奇或同偶。
由于3数码是2*2为矩阵,左右交换不改变逆序,上下交换因为是2是偶数,所有上下交换一次奇偶性改变一次。称空格位置所在的行到目标空格所在的行步数为空格的距离,若两个状态可相互到大,那么这两个状态的逆序奇偶性相同且空格距离为偶数,或 逆序奇偶性不同且空格距离为奇数否则不能。即(状态1的逆序数+空格距离)的奇偶性==状态2的奇偶性。
通过上述条件判断三数码问题是否有解,如果无解,说明无解,否则,进行进行求解,下面通过宽度优先遍历和深度优先遍历来求解。
解题思路1:宽度优先
宽度优先是一层层遍历下来的,在对下层的任以节点进行搜索之前,对本层进行搜完成,可以队列先进先出的性质来模拟此过程。因为每进行搜索前都判断是否有解,第一个节点进入队列,依次遍历队列,直到求解结束。
Begin
1.判断初始状态是否能达到目标状态,若可以,进入2;不可以退出
2.把初始节点放入队列(先进先出)
loop
取得队列最前面的元素;
If 取出元素==目标元素
成功返回并结束;
Else do
Begin
如果队首元素有子女,把队首元素的子女进入队列
End
Until 队列为空
End.
`
import
结果:
2.
解题思路2:深度优先
以栈为容器,由于每次将可能的新状态入栈,并标记为已经搜索到,当一直深入是会遇到下一步可能搜索到的所有状态标记为搜索过,没有入栈的,这条分支路线路线结束。下次弹出栈顶,开启另一条搜索路线,因为每进行搜索前都判断是否有解,所以算法一直执行上述过程,直到找到目标状态
begin:
1.判断初始状态是否能达到目标状态,若可以,进入2;不可以退出
2.把初始节点压入栈(后进先出);
While 栈不空
Begin
弹出栈顶元素;
If 栈顶元素=goal,成功返回并结束;
Else 以任意次序把栈顶元素的子女压入栈中;
End While
import time as tm
#每个位置可交换的位置集合
g_dict_shifts = {0:[1, 2], 1:[0, 3], 2:[0],3:[1,2]}
def swap_chr(a, i, j):
if i > j:
i, j = j, i
#得到ij交换后的数组
b = a[:i] + a[j] + a[i+1:j] + a[i] + a[j+1:]
return b
def solvePuzzle_depth(srcLayout, destLayout):
#先进行判断srcLayout和destLayout逆序值
#这是判断起始状态是否能够到达目标状态。
src=0;dest=0
for i in range(1,4):
fist=0
for j in range(0,i):
if srcLayout[j]>srcLayout[i] and srcLayout[i]!='0':#0是false,'0'才是数字
fist=fist+1
src=src+fist
for i in range(1,4):
fist=0
for j in range(0,i):
if destLayout[j]>destLayout[i] and destLayout[i]!='0':
fist=fist+1
dest = dest + fist
end=0
start = 0
for k in range(4):
if srcLayout[k]=='0':
start =int(k/2)
if destLayout[k]=='0':
end = int(k/2)
dist = abs(end-start)
if ((src+dist)%2)!=(dest%2) :#一个奇数一个偶数,不可达
return -1, None
g_dict_layouts = {}
#初始化字典
g_dict_layouts[srcLayout] = -1
stack_layouts = []
stack_layouts.append(srcLayout)#当前状态存入列表
bFound = False
while len(stack_layouts) > 0:
curLayout = stack_layouts.pop()#出栈
if curLayout == destLayout:#判断当前状态是否为目标状态
break
# 寻找0 的位置。
ind_slide = curLayout.index("0")
lst_shifts = g_dict_shifts[ind_slide]#当前可进行交换的位置集合
for nShift in lst_shifts:
newLayout = swap_chr(curLayout, nShift, ind_slide)
if g_dict_layouts.get(newLayout) == None:#判断交换后的状态是否已经查询过
g_dict_layouts[newLayout] = curLayout
stack_layouts.append(newLayout)#存入集合
lst_steps = []
lst_steps.append(curLayout)
while g_dict_layouts[curLayout] != -1:#存入路径
curLayout = g_dict_layouts[curLayout]
lst_steps.append(curLayout)
lst_steps.reverse()
return 0, lst_steps
if __name__ == "__main__":
#测试数据输入格式
a = []
while len(a) < 4:
x = np.random.randint(0, 5)
if x not in a:
a.append(x)
d = ""
for i in range(len(a)):
d += str(a[i])
srcLayout = d
print('input :',srcLayout)
destLayout = "1203"
print('output :', destLayout)
retCode, lst_steps = solvePuzzle_depth(srcLayout, destLayout)
if retCode != 0:
print("目标布局不可达")
else:
for nIndex in range(len(lst_steps)):
print("step #" + str(nIndex + 1))
print(lst_steps[nIndex][:2])
print(lst_steps[nIndex][2:4])
结果:
1.
2.
两个算法比较:
宽度优先搜索:
时间复杂度
会产生许多不用的节点,效率较低,但是只要问题有解就能找出最优解
深度优先算法:
时间复杂度
如果目标节点不在搜索分支上,而分支不穷大,将得不到解,因此需要最大深度限制;但是如果目标节点在搜索的分枝上可以尽快的找到目标