八数码问题-8puzzle

完整代码及测试数据

完整代码及测试数据

问题简介

八数码:是指在3x3的矩阵中,其中有8个格子放置成1-8,剩下一个格子是空格。能够移动和空格相邻的格子到空格,直到这个矩阵满足每一行依次从左到右读取是有序,得到最后得到1-8有序,最后一个格子是空格。下图展示了一个案例:

8puzzle 4 moves

推广二维N×N的棋盘

对于任意大小的二维N×N的棋盘:

number of inversions + row of blank changes by an even amount when n is even

如何判断问题是否有解?

结论

先说结论:

一个状态表示成一维的形式,求出:除0之外所有数字的逆序数之和,也就是每个数字前面比它大的数字的个数的和,称为这个状态的逆序。

若两个状态的逆序奇偶性相同,则可相互到达,否则不可相互到达。

  1. N是奇数时,当且仅当当前棋盘的逆序对是偶数的时候有解。
  2. N是偶数时,当且仅当当前棋盘的逆序对数加上空格所在的行(行数从0开始算)是奇数的时候有解。
证明
  1. 根据棋局的逆序对定义,不论N为奇数或偶数,空格在同一行的左右移动不会修改逆序对数的奇偶性
  2. 不论N为奇数或偶数,空格上下移动,相当于跨过 N − 1 N-1 N1个格子,那么逆序的改变可能为 ± N − 1 , ± N − 3 , ± N − 5 … … ± N − 2 k − 1 ±N-1,±N-3,±N-5 …… ±N-2k-1 ±N1±N3±N5±N2k1
    • 此时,若N为偶数,空格上下移动,逆序对数的奇偶性必然改变:比如 N = 4 N=4 N=4时,当上下移动的时候,相当于一个数字跨过了另外三个格子,它的逆序可能 ± 3 ±3 ±3 ± 1 ±1 ±1
    • 若N为奇数,空格上下移动,逆序对数的奇偶性保持不变:比如 N = 3 N=3 N=3时,当上下移动的时候,相当于一个数字跨过了另外三个格子,它的逆序可能 ± 2 ±2 ±2 0 0 0
  3. 所以:
    • 当N为奇数时,空格上下左右移动都不改变奇偶性,当前棋盘的逆序对与目标状态逆序对的奇偶性相同时有解,当八数码问题中,目标状态的逆序对数为0(偶数),所以“N是奇数时,当且仅当当前棋盘的逆序对是偶数的时候有解”。
    • N为偶数时,空格每上下移动一次,奇偶性改变。称空格位置所在的行到目标空格所在的行步数为空格的距离(不计左右距离),若两个状态的可相互到达,则有,两个状态的逆序奇偶性相同且空格距离为偶数,或者,逆序奇偶性不同且空格距离为奇数数。否则不能。也就是说,当此表达式成立时,两个状态可相互到达:(状态1的逆序数 + 空格距离)的奇偶性==状态2奇偶性空格距离=N-空格所在行数
计算逆序对

可以利用归并排序时的“合并操作”来统计逆序对:

  1. 双指针 i = 0 , j = 0 i=0,j=0 i=0,j=0分别指向左半部分 l e f t left left和右半部分 r i g h t right right的开始;
  2. 判断 l e f t [ i ] left[i] left[i] r i g h t [ j ] right[j] right[j]的大小:
    • 如果 l e f t [ i ] < = r i g h t [ j ] left[i]<=right[j] left[i]<=right[j],没有逆序对, i + = 1 i+=1 i+=1
    • 如果 l e f t [ i ] > r i g h t [ j ] left[i]>right[j] left[i]>right[j],逆序对数为 m i d − i + 1 mid-i+1 midi+1 j + = 1 j+=1 j+=1
代码
def calInversionNumber(self):
	nums=list(self.board_data.reshape(self.n*self.n))
	for i in range(len(nums)):
		if nums[i]==0:
			del nums[i]
			break
	#归并统计逆序对
	tmp=nums.copy()
	def sortlist(l,r):
		if l>=r:return 0
		mid=l+(r-l)//2
		res=sortlist(l, mid)+sortlist(mid+1, r)
		#合并
		#剪枝
		if nums[mid]<nums[mid+1]:return res
		tmp[l:r+1]=nums[l:r+1]
		i,j=l,mid+1
		for k in range(l,r+1):
			if i==mid+1:
				nums[k]=tmp[j]
				j+=1
			elif j==r+1 or tmp[i]<=tmp[j]:
				nums[k]=nums[i]
				i+=1
			else:
				nums[k]=tmp[j]
				j+=1
				res+=mid-i+1
		return res
	res=sortlist(0, len(nums)-1)
	if self.n&1==1 and res&1==0: return True#奇数且逆序对为偶数
	row,col=self.findzero()
	if self.n&1==0 and (res+row)&1==1:return True
	return False

解法

广度优先遍历穷举:让空格不断和周围位置交换,直到换到棋局变成目标棋局。

A star启发式穷举:优先在队列中从有可能更快达到目标棋局的棋局继续穷举。

广度优先遍历(bfs)

让空格不断和周围位置交换,交换后的棋局加入队列,注意使用哈希集合防止遍历重复的棋局,广度优先遍历结束的树高就是步数。

代码
def solver_bfs(self):
	step=0
	queue=[self.board]
	dxdy=[[-1,0],[0,-1],[1,0],[0,1]]
	visited=set()
	visited.add(self.board.to_string())
	n=self.board.n
	while queue:
		size=len(queue)
		for i in range(size):
			cur=queue.pop(0)
			if cur.is_solvable():
				self.step=step
				self.ans_board=cur
				return 
			#找到0
			row,col=cur.findzero()
			for dx,dy in dxdy:
				x,y=row+dx,col+dy
				if 0<=x<n and 0<=y<n:
					new_board=deepcopy(cur)
					self.swap(new_board.board_data,x,y,row,col)
					new_board.prev=cur
					new_str=new_board.to_string()
					if new_str not in visited:
						queue.append(new_board)
						visited.add(new_str)
					else:
						del new_board
		step+=1
	return -1

A star(A*)

A星算法是一种具备启发性策略的算法,优先在队列中从有可能更快达到目标棋局的棋局继续穷举。

更有可能达到目标棋局的当前棋局得分通过设置代价函数实现,为:已有的代价+未来的代价估计(可以使用曼哈顿、汉明距离等进行度量)。

所以只需要在计算当前棋局的代价,使用优先级队列,优先从代价较小的棋局继续穷举,就可能更快到达目标棋局。

代码
def solver_astar(self):
	queue=[] #优先级队列
	heapq.heappush(queue,self.board)
	dxdy=[[-1,0],[0,-1],[1,0],[0,1]]
	visited=set()
	visited.add(self.board.to_string())
	n=self.board.n
	while queue:
		cur=heapq.heappop(queue)
		if cur.is_solvable():
			self.ans_board=cur
			self.step=self.ans_board.height
			return 
		#找到0
		row,col=cur.findzero()
		for dx,dy in dxdy:
			x,y=row+dx,col+dy
			if 0<=x<n and 0<=y<n:
				new_board=deepcopy(cur)
				self.swap(new_board.board_data,x,y,row,col)
				new_board.prev=cur
				new_str=new_board.to_string()
				new_board.height+=1
				if new_str not in visited:
					heapq.heappush(queue,new_board)
					visited.add(new_str)
				else:
					del new_board
	self.ans_board=None
	return -1

参考资料

AlgorithmRunnig - 八数码 (qq.com)

Slider Puzzle Assignment (princeton.edu)

ZJU2004 Commedia dell’arte - 八数码问题有解的条件及其推广

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值