题意:8数码问题
思路:此题多种方法可解,例如BFS、A* 、 IDA*等等。
法一:bfs,用康托展开来标记判重。所谓康托展开就是将1~n!个整数与n的全排列一一对应起来的一种方法,一种排列的值为i表示这种排列的字典序为第i小。
#include <stdio.h>
#include <string.h>
#define N 362880
int s[9];
int fact[9] = {40320,5040,720,120,24,6,2,1,1};
int ori[4][2] = {{0,-1},{-1,0},{0,1},{1,0}};
int flag[N];
struct queue{
int s[9],dig,flag,pre;
}q[N];
int end;
int contor(int s[9]){//康托展开
int i,j,res,temp;
for(i = 0,res=0;i<8;i++){
for(j = i+1,temp=0;j<9;j++)
if(s[j]<s[i])
temp++;
res += temp * fact[i];
}
return res;
}
int check(int x,int y){
if(x<0||y<0||x>2||y>2)
return 0;
return 1;
}
void print(i){
int j;
if(i!=0){
print(q[i].pre);
if(q[i].flag == 0)
putchar('l');
else if(q[i].flag == 1)
putchar('u');
else if(q[i].flag == 2)
putchar('r');
else if(q[i].flag == 3)
putchar('d');
}
}
int bfs(){
struct queue now;
int i,j,front,rear,x,y,xx,yy,temp,t[9];
front = -1;
rear = 0;
while(front < rear){
now = q[++front];
x = now.dig/3;
y = now.dig%3;
for(i = 0;i<4;i++){
xx = x+ori[i][0];
yy = y+ori[i][1];
if(check(xx,yy)){
for(j = 0;j<9;j++)
t[j] = now.s[j];
t[3*x+y] = t[3*xx+yy];
t[3*xx+yy] = 9;
temp = contor(t);
if(!flag[temp]){
flag[temp] = 1;
q[++rear].dig = 3*xx+yy;
for(j = 0;j<9;j++)
q[rear].s[j] = t[j];
q[rear].pre = front;
q[rear].flag = i;
if(temp == end){
print(rear);
return 1;
}
}
}
}
}
return 0;
}
int main(){
int i,j,k;
char ch;
freopen("a.txt","r",stdin);
memset(flag,0,sizeof(flag));
end = 0;
for(i = 0;i<9;i++){
while((ch = getchar()) && ch==' ');
if(ch == 'x'){
s[i] = 9;
q[0].dig = i;
}
else
s[i] = ch - '0';
}
i = contor(s);
flag[i] = 1;
for(i = 0;i<9;i++)
q[0].s[i] = s[i];
if(!bfs())
printf("unsolvable");
putchar('\n');
return 0;
}
法二:IDA*。IDA*算法是A*算法和迭代加深算法的结合。
迭代加深算法是在dfs搜索算法的基础上逐步加深搜索的深度,它避免了广度优先搜索占用搜索空间太大的缺点,也减少了深度优先搜索的盲目性。它主要是在递归搜索函数的开头判断当前搜索的深度是否大于预定义的最大搜索深度,如果大于,就退出这一层的搜索,如果不大于,就继续进行搜索。这样最终获得的解必然是最优解。
而在A*算法中,我们通过使用合理的估价函数,然后在获得的子节点中选择fCost最小的节点进行扩展,以此完成搜索最优解的目的。但是A*算法中,需要维护关闭列表和开放列表,需要对扩展出来的节点进行检测,忽略已经进入到关闭列表中的节点(也就是所谓的“已经检测过的节点”),另外也要检测是否与待扩展的节点重复,如果重复进行相应的更新操作。所以A*算法主要的代价花在了状态检测和选择代价最小节点的排序上,这个过程中占用的内存是比较大的,一般为了提高效率都是使用hash进行重复状态检测。
而IDA*算法具有如下的特点:综合了A*算法的人工智能性和回溯法对空间的消耗较少的优点,在一些规模很大的搜索问题中会起意想不到的效果。它的具体名称是Iterative Deepening A*, 1985年由Korf提出。该算法的最初目的是为了利用深度搜索的优势解决广度A*的空间问题,其代价是会产生重复搜索。归纳一下,IDA*的基本思路是:首先将初始状态结点的H值设为阈值maxH,然后进行深度优先搜索,搜索过程中忽略所有H值大于maxH的结点;如果没有找到解,则加大阈值maxH,再重复上述搜索,直到找到一个解。在保证H值的计算满足A*算法的要求下,可以证明找到的这个解一定是最优解。在程序实现上,IDA* 要比 A* 方便,因为不需要保存结点,不需要判重复,也不需要根据H值对结点排序,占用空间小。 而IDA*算法中也使用合适的估价函数,来评估与目标状态的距离。
在一般的问题中是这样使用IDA*算法的,当前局面的估价函数值+当前的搜索深度 > 预定义的最大搜索深度时,就进行剪枝。
#include <cstdio>
#include <cmath>
#include <cstdlib>
using namespace std;
int s[10];
int path[10000],len = -1;
int ori[9][4] = {
{-1,1,3,-1},
{-1,2,4,0},
{-1,-1,5,1},
{0,4,6,-1},
{1,5,7,3},
{2,-1,8,4},
{3,7,-1,-1},
{4,8,-1,6},
{5,-1,-1,7},
};
void swap(int &x,int &y){
int tmp = x;
x = y;
y = tmp;
}
int h(){
int sum = 0;
for(int i = 0;i<8;i++){
if(s[i] == 8)
continue;
sum += abs(i/3 - s[i]/3) + abs(i%3 - s[i]%3);
}
return sum;
}
bool has_solution(){
int sum = 0;
for(int i = 1;i<9;i++){
if(s[i] == 8) continue;
for(int j = 0;j<i;j++)
if(s[j] != 8 && s[i] < s[j])
sum++;
}
return (sum&1) == 0;
}
bool is_solution(){
for(int i = 0;i<8;i++)
if(s[i] != i)
return false;
return true;
}
bool IDASTAR(int now, int limit, int x){
if(now >= limit)
return is_solution();
for(int i = 0;i<4;i++){
int xx = ori[x][i];
if(xx == -1)
continue;
if(len>=0 && abs(path[len]-i) == 2)
continue;
swap(s[xx], s[x]);
path[++len] = i;
if(now+h() <= limit && IDASTAR(now+1, limit, xx))
return true;
--len;
swap(s[xx], s[x]);
}
return false;
}
void print(){
for(int i = 0;i<=len;i++){
if(path[i] == 0)
putchar('u');
else if(path[i] == 1)
putchar('r');
else if(path[i] == 2)
putchar('d');
else
putchar('l');
}
putchar('\n');
}
int main(){
char ch;
int state=0, i = 0;
while((ch = getchar()) != EOF){
if(ch == ' ' || ch == '\t' || ch == '\n') continue;
if(ch == 'x'){
state = i;
s[i++] = 8;
}else
s[i++] = ch-'1';
}
if(has_solution()){
int upper = h();
while(!IDASTAR(0, upper, state))
upper++;
print();
}else
printf("unsolvable\n");
return 0;
}