这道题是一道经典的搜索题
第一次做A*算法的题目
这道题需要知道的,第一是怎样把全排列转换为数字,第二是h函数的设计
全排列转化为数字用到的是康托展开,跟逆序数有关,这是一个比较经典的转换。转换成数字的目的就是为了状态的判重,所以A*算法的空间需求总是指数级的
h函数用的是曼哈顿距离的总和,然后f=g+h,g函数就是从初始状态到现在状态花费的步骤数了。
然后使用优先队列,进行BFS。
这道题目,POJ上的数据还是很弱的。所以一般都0ms就水过了,普通的BFS也能很快搜过去。
貌似这道题目是不存在无解的情况,判别的方法其实也有。
判别方法是:
以数组为一维的举例子.
将八数码的一个结点表示成一个数组a[9],空格用0表示,设临时函数p(x)定义为:x数所在位置前面的数比x小的数的个数,
其中0空格不算在之内,那设目标状态为b[9],那r=sigma(p(x)) sigma()表示取所有的x:1-8并求和,
那对于初始状态a[9],t=sigma(p(x)),如果r和t同为奇数或者同为偶数,那么该状态有解,否则无解。
/*
ID: sdj22251
PROG: inflate
LANG: C++
*/
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <cstring>
#include <cmath>
#include <ctime>
#define MAXN 105
#define INF 1000000000
using namespace std;
struct wwj
{
int pre, step, dir, d, pos, id;
char x[12];
bool operator <(const wwj &a) const
{
return a.d < d;
}
wwj(){dir = -1;}
wwj(int u, int v, int w, int y, char t[])
{
pre = u;
step = v;
dir = w;
pos = y;
strcpy(x, t);
}
}p[400000];
int jc[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};
int d[] = {-1, -3, 1, 3};
int v[400000];
char ans[]= "12345678x";
bool move[][4] = {{0,0,1,1}, {1,0,1,1}, {1,0,0,1}, {0,1,1,1}, {1,1,1,1}, {1,1,0,1}, {0,1,1,0}, {1,1,1,0}, {1,1,0,0}};
int hash(wwj &t) //将全排列转化为数字,序列值为0~n!-1,达到了完美的映射
{
int h = 0;
for(int i = 1; i < 9; i++)
{
int count = 0;
for(int j = 0; j <i ; j++)
if(t.x[j] > t.x[i]) count++;
h += count * jc[i];
}
return h;
}
int h(wwj &t)
{
int sum = 0;
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
{
int k = i * 3 + j;
if(t.x[k] == 'x') continue;
sum += abs(i - (t.x[k] - '1') / 3) + abs(j - (t.x[k] - '1') % 3);
}
return sum;
}
void shuchu(wwj t)
{
char res[5000];
int cnt = 0;
while(t.pre != -1)
{
//printf("%s\n", t.x);
if(t.dir == 0)
res[cnt++] = 'l';
else if(t.dir == 1)
res[cnt++] = 'u';
else if(t.dir == 2)
res[cnt++] = 'r';
else if(t.dir == 3)
res[cnt++] = 'd';
t = p[t.pre];
}
res[cnt] = '\0';
reverse(res, res + cnt);
printf("%s\n", res);
}
priority_queue<wwj>q;
int main()
{
wwj tmp;
for(int i = 0; i < 9; i++)
{
scanf("%s", &tmp.x[i]);
if(tmp.x[i] == 'x') tmp.pos = i;
}
tmp.step = 0;
tmp.pre = -1;
tmp.d = tmp.step + h(tmp);
tmp.id = hash(tmp);
p[tmp.id] = tmp;
q.push(tmp);
v[tmp.id] = 1;
while(!q.empty())
{
wwj A = q.top();
q.pop();
if(strcmp(A.x, ans) == 0) {shuchu(A); break;}
for(int i = 0; i < 4; i++)
{
if(move[A.pos][i] == 0) continue;
int xx = A.pos + d[i];
if(xx < 0 || xx >= 9) continue;
char tx[12];
strcpy(tx, A.x);
swap(tx[xx], tx[A.pos]);
wwj B(A.id, A.step + 1, i, xx, tx);
B.id = hash(B);
if(v[B.id]) continue;
v[B.id] = 1;
B.d = h(B) + B.step;
p[B.id] = B;
q.push(B);
}
}
return 0;
}
然后是IDA*算法,IDA*是基于A*的迭代加深搜索,其特点是忽略大于某些深度的状态。如果有解,直接输出,无解的话,放宽松深度限制,继续搜索。
对比A*算法,IDA*无疑是优点巨大的,因为其不需要判重,所以就不需要存储状态,也就节省了巨大的空间。
我的理解就是,设置一个h函数,就是曼哈顿距离的总和,目标状态的h函数值必然是0,则八数码的解决过程总体来说应该是一个h值逐渐变小的过程,所以过程中一些大于这个h值的状态就不搜索了。如果说在这个限制下无解,那么就要放宽h值的限制,中途中可以适当的向一些较大的h值的状态搜索,这就要看,之前最多搜到了哪一步。然后把限制对应的进行修改。
/*
ID: sdj22251
PROG: inflate
LANG: C++
*/
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <cstring>
#include <cmath>
#include <ctime>
#define MAXN 105
#define INF 1000000000
using namespace std;
struct wwj
{
int pos;
char x[12];
}tmp;
int top, found, st[400000], mi;
int d[] = {-1, -3, 1, 3};
char as[] = "lurd";
bool move[][4] = {{0,0,1,1}, {1,0,1,1}, {1,0,0,1}, {0,1,1,1}, {1,1,1,1}, {1,1,0,1}, {0,1,1,0}, {1,1,1,0}, {1,1,0,0}};
int h(wwj &t)
{
int sum = 0;
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
{
int k = i * 3 + j;
if(t.x[k] == 'x') continue;
sum += abs(i - (t.x[k] - '1') / 3) + abs(j - (t.x[k] - '1') % 3);
}
return sum;
}
void output()
{
for(int i = 1;i <= top; i++) printf("%c", as[st[i]]);
printf("\n");
}
void dfs(int p, int dp, int maxdp)
{
int f = h(tmp);
int i, t;
if(mi > f) mi = f;
if(f + dp > maxdp || found) return;
if(f == 0)
{
output();
found = 1;
return;
}
for(i = 0;i < 4; i++)
{
if(move[p][i] == 0) continue;
t = d[i] + p;
if(top > 0 && d[st[top]] + d[i] == 0) continue; //一个重要的剪枝,如果之前的一步和现在的一步是相反的,无疑是做了无用功,不必搜索。
swap(tmp.x[t], tmp.x[p]);
st[++top] = i;
dfs(t, dp + 1, maxdp);
top--;
swap(tmp.x[t], tmp.x[p]);
}
}
void IDA()
{
int maxdp = h(tmp);//初始为初状态的h值
found = 0;
while(found == 0)
{
mi = 0x7fffffff; //mi表示在当前的h值限制下所能搜到的最优位置,即h值最小的位置
top = 0;
dfs(tmp.pos, 0, maxdp);
maxdp += mi; //如果不能搜到就将mi加上去
}
}
int main()
{
for(int i = 0; i < 9; i++)
{
scanf("%s", &tmp.x[i]);
if(tmp.x[i] == 'x') tmp.pos = i;
}
IDA();
return 0;
}