之前写八数码用了广搜,从目标状态扩展路径,把所有可以走到的状态都标记起来,然后输入后直接判断这个状态是否被标记过,但是在poj上正确,在HDU上一直超时,poj时间是5000ms,HDU是1000ms,妥妥的超了,wrong使我进步,又恶补了IDA*的知识。
先介绍一下我理解的IDA*,首先判断当前数码位置状态与目标数码位置状态是否可互达(后附简单介绍),如果不可以直接剪枝,如果可以,就算出当前状态到目标状态所需要走的曼哈顿距离(后面有介绍),那么我们需要移动的距离最少也不会小于这个距离,以最小曼哈顿距离为标尺,如果在最小曼哈顿距离内不能将状态扩展到目标状态就让曼哈顿距离加一继续扩展,不需要使用标记,解决十五数码按这个模板美滋滋。
比如说,在3X3的棋盘中,当前棋盘状态到目标状态理论上5步可以到达,但将所有5步能到达的状态搜完后发现并不能,那我们就在这5步的基础上再往深处搜一步,看是否能到达,一直这样搜下去直到找到目标状态,或者超出题目给的步数限制
曼哈顿距离通俗来讲就是一个数学公式(听起来很高大上,其实大家都会,只不过可能不知道它的名字而已)
A坐标是(ax,ay),B坐标是(bx,by),A和B的曼哈顿距离 = |ax-bx|+|ay-by|;简单吧!
逆序数概念这里就不赘述了,下面是一个通用但不知道叫什么但已经有人证明出的定理,我看过别人证明,但数学不好是硬伤,感兴趣可以自己证明一下,不感兴趣直接用就好
N×N的棋盘
N为奇数时,与八数码问题相同。当前状态逆序数与目标状态逆序数 奇偶同性可互达
N为偶数时,K = 当前0所在的行数 - 目标状态0 所在的行数;
如果K是奇数,当前状态逆序数 与 目标状态逆序数 奇偶性不同 两状态可互达
如果K是偶数,当前状态逆序数 与 目标状态逆序数 奇偶性相同 两状态可互达
也就是说,当此表达式成立时,两个状态可相互到达:(状态1奇偶性==状态2奇偶性)==(空格距离%2==0)
具体代码及注释如下
// IDA*
#include<stdio.h>
#include<string.h>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
char ss[1010]; // 记录路径的字符串
char path[4] = {'u','l','r','d'}; // 上左右下
int dir[4][2] = {-1,0,0,-1,0,1,1,0};// 方向有顺序,为了不回到原位
int en[9][2] = {2,2, // 目标状态在二维数组中的位置
0,0,0,1,0,2,
1,0,1,1,1,2, // en[i][1]数码i在目标状态的列
2,0,2,1
}; // en[i][0]数码i在目标状态的行
int goal[9] = {1,2,3,4,5,6,7,8,0}; // 目标状态
int puzzle[9]; // 当前数码状态
char str[10101]; // 初始数码
int len_limit; // 最小距离限制,初始是曼哈顿距离
int zero_st; // 初始0的位置
int flag; // 标记是否找到解
int mhd() // 曼哈顿距离
{
int cnt=0;
for(int i=0; i<9; i++)
{
if(puzzle[i] != 0)
{
int t = puzzle[i];
int x = i/3;
int y = i%3;
cnt += (abs(x - en[t][0])+abs(y - en[t][1]));
}
}
return cnt;
}
int judge() // 判断目标状态是否有解
{
int cnt=0;
int k=0;
for(int i=0; i<9; i++)
{
for(int j=i+1; j<9; j++)
{
if(puzzle[i] > puzzle[j] && puzzle[j])
{
cnt++;
}
}
}
return !((k+cnt)%2);
}
void dfs(int dd,int per_dir,int len)
{
if(flag)
return;
if(len == len_limit) // 如果已经扩展到限定距离且曼哈顿距离是0
{
if(mhd() == 0)
{
flag=1;
return;
}
return ;
}
for(int i=0; i<4; i++)
{
if(i+per_dir == 3) // 如果本次移动方向与上一次相反,跳过
continue; // 如果上次向上,这次向下相当于回到原位
int dx = dd/3 + dir[i][0];
int dy = dd%3 + dir[i][1];
int dz = dx*3 + dy;
if(dx>=0 && dy>=0 && dx<3 && dy<3)
{
swap(puzzle[dz],puzzle[dd]); // 把0和其它数码交换位置
if(len + mhd() <= len_limit && !flag) // 如果当前已经移动的距离+当前状态的曼哈顿距离小于最小距离限制
{
ss[len] = path[i];
dfs(dz,i,len+1);
if(flag)
return ;
}
swap(puzzle[dz],puzzle[dd]); // 如果以上无解,交换回来
}
}
}
int main()
{
gets(str);
int j=0;
for(int i=0; i<strlen(str); i++)
{
if(str[i] != ' ')
{
if(str[i] == 'x')
{
zero_st = j;
puzzle[j++] = 0;
}
else
puzzle[j++] = str[i]-'0';
}
}
if(judge())
{
flag=0;
len_limit=mhd();
while(!flag) // 如果最小距离不能到达目标状态,最小距离加一,多走一步看能否到达
{
len_limit++;
dfs(zero_st,-1,0);
if(flag)
break;
}
}
if(flag)
printf("%s\n",ss);
}