参考文章:
1. DBFS,A*,IDA*,转自链接 (侵删)
2. 一份非常详细的实验报告:http://blog.csdn.net/ray58750034/article/details/599897#comments
题目链接:
http://poj.org/problem?id=2449
分析:
又称为九宫格问题,给出一个状态的九宫格,判断是否有解,如果有则输出解。
八数码问题有无解的结论:
一个状态表示成一维的形式,求出除0之外所有数字的逆序数之和(每个数字前面比它大的数字的个数的和,称为这个状态的逆序)。
若两个状态的逆序数奇偶性相同,则可相互到达,否则不可相互到达。由于原始状态的逆序为0(偶数),则逆序为偶数的状态有解。
证明:当左右移动空格时,逆序数不变。当上下移动空格时,相当于将一个数字向前(或向后)移动两格,跳过 的这两个数字要么都比它大(小),逆序可能±2;要么一个较大一个较小,逆序不变。所以可得结论:只要是相互可达的两个状态,它们的逆序奇偶性相同。详细的证明请参考:
http://blog.csdn.net/tiaotiaoyly/article/details/2008233
康拓展开(将每一种排列都康拓展开,得到与其唯一对应的一个数,是对于全排列的一种很好的哈希函数):
公式: X=an×(n−1)!+an−1×(n−2)!+...+ai×(i−1)!+...+a2×1!+a1×0! 其中,ai表示的是第i-1个数(从0开始数),在这个排列中的大小位置(从0开始计算)。
代码如下:
int fac[] = { 1,1,2,6,24,120,720,5040,40320 };//打表表示前几个数的阶乘。
int Cantor( int s[])//参数为排列。
{
int sum = 0;
for(int i = 0;i<9;i++)
{
int cnt = 0;
for(int j=i+1;j<9;j++)
{
if( s[i] > s[j] ) cnt++;
}
sum+=(cnt*fac[9-i-1]);
}
return sum+1;
}
通过康拓逆展开生成与其对应的全排列:
如果已知一个排列的康拓展开数,那么可以使用辗转相除的方法得到这个排列中的每一个 ai :
如果排列中的元素为A,B,C,D,那么a4 = 3表示剩余元素为4的时候它排在第三位(均从第0位开始计数),即D;a3 = 1表示剩余元素为3的时候它排再第一位,即B;a2 = 0表示剩余元素为2的时候它排在第0位,即A,最后一个a1就是C了。
BFS + 康拓展开 解决八数码问题:
#include
#include
#include
#include
#include
#include
// 正向广度搜索
using namespace std;
const int maxn = 1000000;
int fac[] = { 1,1,2,6,24,120,720,5040,40320 };
bool flag [maxn];
int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
string cmd = "udlr";
int aim=46234;
int Cantor( int s[])
{
int sum = 0;
for(int i = 0;i<9;i++)
{
int cnt = 0;
for(int j=i+1;j<9;j++)
{
if( s[i] > s[j] ) cnt++;
}
sum+=(cnt*fac[9-i-1]);
}
return sum+1;
}
struct node
{
int s[9];
int loc;
int status;
string path;
};
node origin;
string path;
int BFS()
{
queue q;
while ( !q.empty() )
q.pop();
node cur,temp;
q.push(origin);
int x,y;
while( !q.empty() )
{
cur = q.front();
q.pop();
if( cur.status == aim )
{
path = cur.path;
return 1;
}
x = cur.loc/3;
y = cur.loc%3;
for(int i=0;i<4;i++)
{
int tx = x + dir[i][0];
int ty = y + dir[i][1];
if( tx<0 || ty <0 || tx>2 || ty>2 ) continue;
temp = cur;
temp.loc = tx*3 + ty;
temp.s[cur.loc] = temp.s[temp.loc];
temp.s[temp.loc] = 0;
temp.status = Cantor(temp.s);
if( !flag[temp.status] )
{
flag[temp.status] = 1;
temp.path = temp.path + cmd[i];
if( temp.status == aim )
{
path = temp.path;
return 1;
}
q.push(temp);
}
}
}
return 0;
}
int main()
{
char ch;
while(cin>>ch)
{
if(ch == 'x')
{
origin.s[0] = 0;
origin.loc = 0;
}
else origin.s[0] = ch - '0';
for(int i=1;i<9;i++)
{
cin >> ch;
if( ch == 'x' )
{
origin.s[i] = 0;
origin.loc = i;
}
else origin.s[i] = ch - '0';
}
origin.status = Cantor( origin.s );
memset( flag , 0 ,sizeof(flag) );
if(BFS())
cout<else
printf("unsolvable\n");
}
return 0;
}
双向广搜 :
由于目标态和初始态都已知,可以采用从两个状态同时开始搜,当搜到同一个节点时,搜索结束,将两边的步数加起来输出。这道题里在每个节点,用一个值标记,此节点是由哪个状态访问的,故只需用一个队列交替扩展。双向广搜会少扩展许多节点,时间效率得到大幅提升。
#include
#include
#include
#include
#define FUCK puts("fuck!")
#define STOP system("pause")
#define MAXN 388211
using namespace std;
struct state{
int sta,pos,step;
int visit;
}st[MAXN],source,target;
int temp[10],tar[10],sou[10];
int d[9][4]={{0,4,0,2},{0,5,1,3},{0,6,2,0},{1,7,0,5},{2,8,4,6},{3,9,5,0},{4,0,0,8},{5,0,7,9},{6,0,8,0}};
int num;
int convert(int a[],state &b)
{
b.sta=0;
for(int i = 0; i < 9; i ++)
{
if(a[i]!=0)
b.sta |=((a[i]-1)<<(24-i*3));
else
{
b.pos = i;
b.sta |=(a[i]<<(24-i*3));
}
}
return 1;
}
state exchange(state a,int pos)
{
int temp = 7<<((9-pos)*3);
state s;
s.sta = a.sta;
temp = temp & a.sta;
temp = ((temp>>((9-pos)*3))<<((9-a.pos-1)*3));
s.sta |= temp;
s.sta &= ~(7<<((9-pos)*3));
s.pos = pos-1;
return s;
}
int search(state a)
{
int index = a.sta%MAXN;
bool flag = true;
while(flag)
{
if(!st[index].sta)
{
st[index].sta = a.sta;
st[index].pos = a.pos;
flag = false;
}
else if(!(st[index].sta^a.sta)&&!(st[index].pos^a.pos))
flag = false;
else
index = (index+1)%MAXN;
}
return index;
}
int main()
{
freopen("in.txt","r",stdin);
clock_t start,end;
for(int j=0;j<8;j++)temp[j] = j+1;
temp[8]=0;
convert(temp,target);
while(1)
{
int i = 0;
memset(st,0,sizeof(st));
char ch;
while((ch=getchar())!='\n')
{
if(ch<='9'&&ch>='0')
sou[i++] = ch - '0';
else if(ch=='x')
sou[i++] =0;
}
convert(sou,source);
start = clock();
i = search(source);
queue<int>q;
q.push(i);
i = search(target);
st[i].visit = 1;
st[i].step = 1;
q.push(i);
if(!(source.sta^target.sta)&&!(source.pos^target.pos))
{
printf("0\n");
while(!q.empty())q.pop();
continue;
}
int index;
int count = 0;
bool isSolve = false;
while(!q.empty()&&!isSolve)
{
count ++;
index = q.front();
for(int j = 0; j < 4; j ++)
{
if(d[st[index].pos][j])
{
int flag = search(exchange(st[index],d[st[index].pos][j]));
if(!st[flag].step)
{
st[flag].step = st[index].step + 1;
st[flag].visit = st[index].visit;
q.push(flag);
}
else
{
if(st[flag].visit^st[index].visit)
{
isSolve = true;
printf("%d\n",st[index].step+st[flag].step);
}
}
}
}
q.pop();
}
while(!q.empty())q.pop();
end = clock();
printf("Time:%dms\nstate number:%d\n",end-start,count);
}
system("pause");
return 0;
}
A*搜索:
有两种可行的启发函数: 不在位数(difference)与曼哈顿距离(manhattan)
在每一个节点中,加一个int型变量存储此节点的估价。此时用优先队列存储节点。但是若启发函数效率不高,减少的扩展节点的时间可能还不足以抵过小顶堆调整的时间,结果就是时间效率可能比普通的bfs还差。
基于不在位函数的A*搜索:
#include
#include
#include
#include
#define FUCK puts("fuck!")
#define STOP system("pause")
#define MAXN 388211
using namespace std;
struct state{
int sta,pos,step;
int f;
}st[MAXN],source,target;
int temp[10],tar[10],sou[10];
int d[9][4]={{0,4,0,2},{0,5,1,3},{0,6,2,0},{1,7,0,5},{2,8,4,6},{3,9,5,0},{4,0,0,8},{5,0,7,9},{6,0,8,0}};
int num;
int h(state a)
{
int temp = target.sta;
int cnt=0;
for(int i = 0;i < 9; i ++)
{
if(a.pos==target.pos)
{
if(!(((temp>>(3*i))&7)^((a.sta>>(3*i))&7)))
cnt++;
}
else
{
if((!(((temp>>(3*i))&7)^((a.sta>>(3*i))&7)))&&((a.sta>>(3*i))&7))
cnt++;
}
}
return 9-cnt;
}
struct cmp
{
bool operator () (int u, int v)
{
return st[u].f > st[v].f;
}
};
int convert(int a[],state &b)
{
b.sta=0;
for(int i = 0; i < 9; i ++)
{
if(a[i]!=0)
b.sta |=((a[i]-1)<<(24-i*3));
else
{
b.pos = i;
b.sta |=(a[i]<<(24-i*3));
}
}
return 1;
}
state exchange(state a,int pos)
{
int temp = 7<<((9-pos)*3);
state s;
s.sta = a.sta;
temp = temp & a.sta;
temp = ((temp>>((9-pos)*3))<<((9-a.pos-1)*3));
s.sta |= temp;
s.sta &= ~(7<<((9-pos)*3));
s.pos = pos-1;
return s;
}
int search(state a)
{
int index = a.sta%MAXN;
bool flag = true;
while(flag)
{
if(!st[index].sta)
{
st[index].sta = a.sta;
st[index].pos = a.pos;
flag = false;
}
else if(!(st[index].sta^a.sta)&&!(st[index].pos^a.pos))
flag = false;
else
index = (index+1)%MAXN;
}
return index;
}
int main()
{
freopen("in.txt","r",stdin);
clock_t start,end;
for(int j=0;j<8;j++)temp[j] = j+1;
temp[8]=0;
convert(temp,target);
while(1)
{
int i = 0;
memset(st,0,sizeof(st));
char ch;
while((ch=getchar())!='\n')
{
if(ch<='9'&&ch>='0')
sou[i++] = ch - '0';
else if(ch=='x')
sou[i++] =0;
}
convert(sou,source);
start = clock();
i = search(source);
st[i].f = h(st[i]);
priority_queue<int,vector<int>,cmp>q;
q.push(i);
int index;
int count = 0;
while(!q.empty())
{
count++;
index = q.top();
q.pop(); //!!!!
if(!(st[index].sta^target.sta)&&st[index].pos == target.pos)
{
printf("%d\n",st[index].step);
break;
}
for(int j = 0; j < 4; j ++)
{
if(d[st[index].pos][j])
{
int flag = search(exchange(st[index],d[st[index].pos][j]));
if(!st[flag].step||st[flag].step > st[index].step + 1)
{
st[flag].step = st[index].step + 1;
st[flag].f = st[flag].step + h(st[flag]);
q.push(flag);
}
}
}
}
while(!q.empty())q.pop();
end = clock();
printf("Time:%dms\nstate number:%d\n",end-start,count);
}
system("pause");
return 0;
}
基于 manhattan距离函数的A*搜索:
#include
#include
#include
#include
#define FUCK puts("fuck!")
#define STOP system("pause")
#define MAXN 388211
using namespace std;
struct state{
int sta,pos,step;
int f;
}st[MAXN],source,target;
int temp[10],tar[10],sou[10];
int d[9][4]={{0,4,0,2},{0,5,1,3},{0,6,2,0},{1,7,0,5},{2,8,4,6},{3,9,5,0},{4,0,0,8},{5,0,7,9},{6,0,8,0}};
int num;
int manhattan[10][10] = //第i个数及其所处不同位置的Manhattan路径长度
{
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1},
{-1, 0, 1, 2, 1, 2, 3, 2, 3, 4},
{-1, 1, 0, 1, 2, 1, 2, 3, 2, 3},
{-1, 2, 1, 0, 3, 2, 1, 4, 3, 2},
{-1, 1, 2, 3, 0, 1, 2, 1, 2, 3},
{-1, 2, 1, 2, 1, 0, 1, 2, 1, 2},
{-1, 3, 2, 1, 2, 1, 0, 3, 2, 1},
{-1, 2, 3, 4, 1, 2, 3, 0, 1, 2},
{-1, 3, 2, 3, 2, 1, 2, 1, 0, 1},
{-1, 4, 3, 2, 3, 2, 1, 2, 1, 0}
};
int h(state a)
{
int cnt=0;
for(int i = 0;i < 9; i ++)
{
if(a.pos != i)
cnt += manhattan[((a.sta>>(3*(8-i)))&7)+1][i+1];
}
return cnt;
}
class cmp
{
public:
bool operator () (int u, int v)
{
return st[u].f > st[v].f;
}
};
int convert(int a[],state &b)
{
b.sta=0;
for(int i = 0; i < 9; i ++)
{
if(a[i]!=0)
b.sta |=((a[i]-1)<<(24-i*3));
else
{
b.pos = i;
b.sta |=(a[i]<<(24-i*3));
}
}
return 1;
}
state exchange(state a,int pos)
{
int temp = 7<<((9-pos)*3);
state s;
s.sta = a.sta;
temp = temp & a.sta;
temp = ((temp>>((9-pos)*3))<<((9-a.pos-1)*3));
s.sta |= temp;
s.sta &= ~(7<<((9-pos)*3));
s.pos = pos-1;
return s;
}
int search(state a)
{
int index = a.sta%MAXN;
bool flag = true;
while(flag)
{
if(!st[index].sta)
{
st[index].sta = a.sta;
st[index].pos = a.pos;
flag = false;
}
else if(!(st[index].sta^a.sta)&&!(st[index].pos^a.pos))
flag = false;
else
index = (index+1)%MAXN;
}
return index;
}
int main()
{
freopen("in.txt","r",stdin);
clock_t start,end;
for(int j=0;j<8;j++)temp[j] = j+1;
temp[8]=0;
convert(temp,target);
while(1)
{
int i = 0;
memset(st,0,sizeof(st));
char ch;
while((ch=getchar())!='\n')
{
if(ch<='9'&&ch>='0')
sou[i++] = ch - '0';
else if(ch=='x')
sou[i++] =0;
}
convert(sou,source);
start = clock();
i = search(source);
st[i].f = h(st[i]);
priority_queue<int,vector<int>,cmp>q;
q.push(i);
int index;
int count = 0;
while(!q.empty())
{
count++;
index = q.top();
q.pop(); //!!!!
if(!(st[index].sta^target.sta)&&st[index].pos == target.pos)
{
printf("%d\n",st[index].step);
break;
}
for(int j = 0; j < 4; j ++)
{
if(d[st[index].pos][j])
{
int flag = search(exchange(st[index],d[st[index].pos][j]));
if(!st[flag].step||st[flag].step > st[index].step + 1)
{
st[flag].step = st[index].step + 1;
st[flag].f = st[flag].step + h(st[flag]);
q.push(flag);
}
}
}
}
while(!q.empty())q.pop();
end = clock();
printf("Time:%dms\nstate number:%d\n",end-start,count);
}
system("pause");
return 0;
}
IDA*搜索:
由于普通的深搜在此问题上,要么搜索到错误的结果,要么需要搜索所有的状态,才能确定是否是最优,故在这里使用IDA*。IDA*是一种迭代加深的深度搜索,若在此深度下没有搜到目标点,则将深度加一重新搜索。无须状态判重,无需估价排序,用不到哈希表,堆上也不必应用,空间需求变的超级少,实现也最简单。在深搜过程中,根据启发函数做剪枝,可以使效率达到很高。另外在求路径的时候,IDA*也是最方便的。
基于不在为函数的IDA*搜索:
#include
#include
#include
#include
#define FUCK puts("fuck!")
#define STOP system("pause")
#define MAXN 388211
using namespace std;
struct state{
int sta,pos;
}source,target;
int temp[10],tar[10],sou[10];
int pathLimit;
int cnt;
int d[9][4]={{0,4,0,2},{0,5,1,3},{0,6,2,0},{1,7,0,5},{2,8,4,6},{3,9,5,0},{4,0,0,8},{5,0,7,9},{6,0,8,0}};
int h(state a)
{
int temp = target.sta;
int cnt=0;
for(int i = 0;i < 9; i ++)
{
if(a.pos==target.pos)
{
if(!(((temp>>(3*i))&7)^((a.sta>>(3*i))&7)))
cnt++;
}
else
{
if(!(((temp>>(3*i))&7)^((a.sta>>(3*i))&7))&&((a.sta>>(3*i))&7))
cnt++;
}
}
return 9-cnt;
}
int convert(int a[],state &b)
{
b.sta=0;
for(int i = 0; i < 9; i ++)
{
if(a[i]!=0)
b.sta |=((a[i]-1)<<(24-i*3));
else
{
b.pos = i;
b.sta |=(a[i]<<(24-i*3));
}
}
return 1;
}
state exchange(state a,int pos)
{
int temp = 7<<((9-pos)*3);
state s;
s.sta = a.sta;
temp = temp & a.sta;
temp = ((temp>>((9-pos)*3))<<((9-a.pos-1)*3));
s.sta |= temp;
s.sta &= ~(7<<((9-pos)*3));
s.pos = pos-1;
return s;
}
bool IDAStar(state &a,int depth,int diff,int prepos)
{
cnt++;
if(!(a.sta^target.sta)&&a.pos == target.pos)
{
printf("%d\n",depth);
return true;
}
if(depth >= pathLimit) return false;
if( depth + diff > pathLimit ) return false;
for(int j = 0; j < 4; j ++)
{
if(d[a.pos][j] == prepos+1) continue;
if(d[a.pos][j])
{
state next = exchange(a,d[a.pos][j]);
if(IDAStar(next,depth+1, h(next),a.pos))
return true;
}
}
return false;
}
int main()
{
freopen("in.txt","r",stdin);
clock_t start,end;
int diff = 0;
for(int j=0;j<8;j++)temp[j] = j+1;
temp[8]=0;
convert(temp,target);
while(1)
{
int i = 0;
char ch;
while((ch=getchar())!='\n')
{
if(ch<='9'&&ch>='0')
sou[i++] = ch - '0';
else if(ch=='x')
sou[i++] =0;
}
start = clock();
cnt = 0;
convert(sou,source);
pathLimit = h(source);
diff = pathLimit;
while(!IDAStar(source,0,diff,-1))pathLimit++;
end = clock();
printf("Time:%dms\nstate number:%d\n",end-start,cnt);
}
system("pause");
return 0;
}
基于manhattan距离函数的搜索:
#include
#include
#include
#include
#define FUCK puts("fuck!")
#define STOP system("pause")
#define MAXN 388211
using namespace std;
struct state{
int sta,pos;
}source,target;
int temp[10],tar[10],sou[10];
int pathLimit;
int d[9][4]={{0,4,0,2},{0,5,1,3},{0,6,2,0},{1,7,0,5},{2,8,4,6},{3,9,5,0},{4,0,0,8},{5,0,7,9},{6,0,8,0}};
int manhattan[10][10] = //第i个数及其所处不同位置的Manhattan路径长度
{
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1},
{-1, 0, 1, 2, 1, 2, 3, 2, 3, 4},
{-1, 1, 0, 1, 2, 1, 2, 3, 2, 3},
{-1, 2, 1, 0, 3, 2, 1, 4, 3, 2},
{-1, 1, 2, 3, 0, 1, 2, 1, 2, 3},
{-1, 2, 1, 2, 1, 0, 1, 2, 1, 2},
{-1, 3, 2, 1, 2, 1, 0, 3, 2, 1},
{-1, 2, 3, 4, 1, 2, 3, 0, 1, 2},
{-1, 3, 2, 3, 2, 1, 2, 1, 0, 1},
{-1, 4, 3, 2, 3, 2, 1, 2, 1, 0}
};
int h(state a)
{
int cnt=0;
for(int i = 0;i < 9; i ++)
{
if(a.pos != i)
cnt += manhattan[((a.sta>>(3*(8-i)))&7)+1][i+1];
}
return cnt;
}
int convert(int a[],state &b)
{
b.sta=0;
for(int i = 0; i < 9; i ++)
{
if(a[i]!=0)
b.sta |=((a[i]-1)<<(24-i*3));
else
{
b.pos = i;
b.sta |=(a[i]<<(24-i*3));
}
}
return 1;
}
state exchange(state a,int pos)
{
int temp = 7<<((9-pos)*3);
state s;
s.sta = a.sta;
temp = temp & a.sta;
temp = ((temp>>((9-pos)*3))<<((9-a.pos-1)*3));
s.sta |= temp;
s.sta &= ~(7<<((9-pos)*3));
s.pos = pos-1;
return s;
}
bool IDAStar(state &a,int depth,int diff,int prepos)
{
if(!(a.sta^target.sta)&&a.pos == target.pos)
{
printf("%d\n",depth);
return true;
}
if(depth > pathLimit) return false;
if( depth + diff > pathLimit ) return false;
for(int j = 0; j < 4; j ++)
{
if(d[a.pos][j] == prepos+1) continue;
if(d[a.pos][j])
{
state next = exchange(a,d[a.pos][j]);
if(IDAStar(next,depth+1, h(next),a.pos))
return true;
}
}
return false;
}
int main()
{
freopen("in.txt","r",stdin);
clock_t start,end;
int diff = 0;
for(int j=0;j<8;j++)temp[j] = j+1;
temp[8]=0;
convert(temp,target);
while(1)
{
int i = 0;
char ch;
while((ch=getchar())!='\n')
{
if(ch<='9'&&ch>='0')
sou[i++] = ch - '0';
else if(ch=='x')
sou[i++] =0;
}
start = clock();
convert(sou,source);
pathLimit = h(source);
diff = pathLimit;
while(!IDAStar(source,0,diff,-1))pathLimit++;
end = clock();
printf("Time:%dms\ndepthlimit:%d\n",end-start,pathLimit);
}
system("pause");
return 0;
}