15 Puzzle(15 数码,IDA* 及 N 数码的 有解无解的判读)

The goal of the 15 puzzle problem is to complete pieces on 4×44×4 cells where one of the cells is empty space.

In this problem, the space is represented by 0 and pieces are represented by integers from 1 to 15 as shown below.

1 2 3 4
6 7 8 0
5 10 11 12
9 13 14 15

You can move a piece toward the empty space at one step. Your goal is to make the pieces the following configuration in the shortest move (fewest steps).

1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 0

Write a program which reads an initial state of the puzzle and prints the fewest steps to solve the puzzle.

Input

The 4×44×4 integers denoting the pieces or space are given.

Output

Print the fewest steps in a line.

Constraints

  • The given puzzle is solvable in at most 45 steps.

Sample Input

1 2 3 4
6 7 8 0
5 10 11 12
9 13 14 15

Sample Output

8

题意:给你一行16个数,四行四列,让你看看经过多少步变为 题意上给出的固定的 目标状态;

思路:刚开始 刚开始我没有学 IDA*时,我觉着 用 bfs()用康拓展开式,或用 map标记状态;但是想想 一定超时,因为有16! 种状态,把很多必要的状态都 存上了,不是超时,就是 内存超限; 

下面介绍一下: IDA*  迭代加深搜索(ID-DFS),其实先估算一个 从起始状态 到 目标状态的 最小距离 limit ,一般会有函数

当前状态v下 到 目标状态 的估算函数  d(v) + h*(v) <=limit , 一般情况下, d(v) 为从起始状态到达当前状态所递归调用的深度;  h*(v) 表示从当前v状态到 目标状态的 最小距离(估算值),limit 为当前 从起始状态到目标状态的 最小距离(估算值),要是找不到的话,一直枚举limit,让 limit  加 1,再从头再找,直到 达到目标状态,第一次找到 目标状态就是最优解,因为我们是枚举的 limit ,一次找到当然是最优解;

 在这道题中可能有人会问 limit每次加1,都要从头再搜,这难道不更耗时时吗,还有就是在代码中,根本就都没有判重状态, 就是因为这道题 判重不易,所以选择了 IDA*(迭代加深搜索),当limit小的时候,因为limit的限制,所以能搜到的状态数不会太多,还有这道题 会有 16! 种状态,你要是用 map 判重,log N 的复杂度,16! 为一个  以2开头的十四位数字,是 2的40次方大的数,要查询大约 40次左右,而 IDA* 就算是不判重,是因为我们考虑搜索树中的一个节点,如果它(假设我们做一个简单的剪枝、拒绝反着走到达当前状态的最后一步)被再次搜到,就意味着它至少又转了四下。也就是说一个点至多被搜50413

,即其常数约为13!也就是说这其实是比用平衡树判重快得多的。但是ID-A*是不能像A*一样每次取出预估最优的状态的,它只能通过调整上下来解决这个问题;这跟DFS与ID-DFS的区别还不一样,因为在一个预估较劣的状态可能会达到一个预估较优的状态。

下面 介绍如何判断给出的起始 N数码能否到达到给出的目标状态

先介绍一下逆序数:在一列数中,每个数前面有几个比自己大的数,就是这个数的逆序数,这一列数的逆序数就是这一列数的逆序数的总和;

先给出结论 :

在算N数码的逆序数时,不把0算入在内;

当N为奇数时, 当 两个N数码的逆序数 奇偶性相同时,可以互达,否则不行;

当N为偶数时,当 两个N数码的奇偶性相同的话,那么两个N数码中的0所在行的差值 k,k也必须是偶数时,才能互达;

                        当两个N数码的奇偶性不同时,那么两个N数码中的0所在行的差值 k,k也必须是奇数时,才能互达;

为什么呢:因为当0左右移动时,这个N数码的逆序数是不变的, 当上下移动时,当N为奇数时,上下移动时,中间有N-1个数,N-1 为偶数,那么整个N数码的逆序数只会有两种可能 加减一个偶数;举例说明:(当N为3时,N-1为2,当0上下移动时,中间的两个数的逆序数 有三种可能,同时加 1 或同时减1 ,或 一个加1,一个减1,这三种情况都使得总体的逆序数 增加或减少偶数个,所以不管 上下移几次,总体的逆序数的奇偶性是不变得)。当N为偶数时,上下移动时,中间有N-1个数,N-1为奇数,上下移动一次整个N数码的逆序数只会有 加上或减去一个奇数;举例说明:(当N为4时,N-1为3,当0上下移动时,中间的3个数的逆序数有四种情况,0个增加1、3个减少1,1个增加1、2个减少1,2个增加1,1个减少1,3个增加1、0个减少1,这四种情况全部都是 使全部的逆序数增加或减去 一个奇数),有了这个分析上的结论就好理解了

在写这个代码时,还有注意3点:找逆序数时不能算0的 逆序数;  找曼哈顿距离时,不能算0的曼哈顿距离; 在搜索时,一定要注意不能回搜; 

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#include<stdlib.h>
#define ll long long 
const int Max = 4;

int a[4][2] = {0,-1,1,0,0,1,-1,0};
int goal[16][2]={{3,3},{0,0},{0,1},{0,2},{0,3},{1,0},{1,1},{1,2},{1,3},{2,0},{2,1},{2,2},{2,3},{3,0},{3,1},{3,2}};
 // 目标状态的数字所在位置
int mpp[Max][Max],mpp2[Max*Max]; 

int flag = 0,limit,Mi;
// 曼哈顿距离为 所有的数字要走到目标状态,最少要和0换的次数; 
int ddd(int mpp[Max][Max])
{
	int i,j;
	int sum = 0;
	for(i = 0;i<4;i++)
	{
		for(j = 0;j<4;j++)
		{
			if(mpp[i][j]!=0)    //判断曼哈顿距离不能判断0; 
				sum += abs(i-goal[mpp[i][j]][0])+abs(j - goal[mpp[i][j]][1]); 
		}
	}
	return sum;
}
int nix(int mpp2[Max*Max])
{
	int i,j;
	int x,y;
	int sum = 0;
	for(i =0;i<Max*Max;i++)
	{
		if(mpp2[i]==0)
		{
			y = i/4;
			x = i%4;
			continue;
		}
		for(j = i+1;j<Max*Max;j++)
		{
			if(mpp2[j]==0) continue;
			if(mpp2[i]>mpp2[j])
				sum++;
		}
	}
	return sum;
	
}

void dfs(int y,int x,int len,int f)   // x,y当前0的坐标 len 为已经走了几步了; 
{									 	// f为当上一次的搜索方向 为了不回搜, 
	int d = ddd(mpp);
	if(flag) return;
	if(len<=limit)
	{
		if(d==0)
		{
			flag = 1;
			Mi = len;
			return ;
		}
		if(len==limit) return;
	}
	
	for(int i = 0;i<4;i++)
	{
		int tx = x + a[i][0];
		int ty = y + a[i][1];
		if(tx>=0&&ty>=0&&ty<4&&tx<4&&((f==-1)||i!=(f+2)%4))
		{
			swap(mpp[y][x],mpp[ty][tx]);
			if(len+ddd(mpp)<=limit)   // IDA* 减值,当前走的步数 加上 当前状态到达标状态的最小步数, 
			{						// 要小于等于 当前枚举到的最小的 从起始状态到达目标状态的步数,不能超过; 
				dfs(ty,tx,len+1,i);
				if(flag) return ;
			}
			swap(mpp[y][x],mpp[ty][tx]);
		}
	}
}
int main()
{
	int i,j;
	while(~scanf("%d",&mpp2[0]))
	{
		int y,x;
		mpp[0][0] = mpp2[0];
		if(mpp2[0]==0)
		{
			y = 0;
			x = 0;
		}
		for(i = 1;i<Max*Max;i++)
		{
			scanf("%d",&mpp2[i]);
			mpp[i/4][i%4] = mpp2[i];
			if(mpp2[i]==0)
			{
				y = i/4;
				x = i%4;
			}
		}
		int k = abs(y-goal[0][0]);//+abs(x-goal[0][0]);
		
		int pp = nix(mpp2);
		
		int f = 1;
		if((pp+k)%2!=0)
			f = 0;
		if(!f)
		{
			//printf("pp==%d\n",pp);
			//printf("00000000000\n");
			continue; //因为题意说过了,不会有不会到达的这种情况,但是我还是判断了
		}
		flag = 0;
		limit = ddd(mpp);    // 当前要达到目标状态的最小步数; 
		while(!flag&&limit<=55)  
		{
			dfs(y,x,0,-1);
			if(!flag)
				limit ++;    // 枚举最小步数; 
		}
		if(flag)
			printf("%d\n",Mi);
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值