参考
1.http://www.cnblogs.com/goodness/archive/2010/05/04/1727141.html 八数码的八境界
2.https://www.cnblogs.com/zufezzt/p/5659276.html HDU 1043 八数码(八境界)
以下仅为个人理解,不能保证正确
境界1:暴力广搜+STL(HDU1043 G++ 爆内存 C++ 1248ms 32568kb AC)
说实话这个能过真的是太秀了鬼鬼,差200kb就爆炸
由于八数码的状态总共也就9!=362880,所以完全可以采用set判重,string存储状态,map存储到终态需要的步数
int dy[4]={-1,1,0,0};
int dx[4]={0,0,-1,1};
string dir="durl";
set<string> f;
map<string,string> m;
从最终状态开始”12345678x”,倒着记录
m["12345678x"]="";
f.insert("12345678x");
然后就是bfs咯
代码如下:
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<iomanip>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int MAX_N=100000;
const int INF = 0x3f3f3f3f;
#define eps 1e-7
short dy[4]={-1,1,0,0};
short dx[4]={0,0,-1,1};
string dir="durl";//这里方向和题中给的要反一下哦
set<string> f;
map<string,string> m;
struct node{
string s;
string path;
int pos;
node(){}
node(string str,string pa,int Pos){
s=str;
path=pa;
pos=Pos;
}
};
void pre(){
queue<node> q;
q.push(node("12345678x","",8));
m["12345678x"]="";
f.insert("12345678x");
while(!q.empty()){
node h=q.front();
q.pop();
short a=h.pos/3;//模拟一下上下左右
short b=h.pos%3;
for(int i=0;i<4;i++){
short y=a+dy[i];
short x=b+dx[i];
if(y<0||y>2||x<0||x>2)//判断是否在范围内
continue;
short pos=3*y+x;
swap(h.s[h.pos],h.s[pos]);
if(f.find(h.s)==f.end()){//使用set进行判重
q.push(node(h.s,dir[i]+h.path,pos));
f.insert(h.s);
m[h.s]=dir[i]+h.path;
}
swap(h.s[h.pos],h.s[pos]);//这里在记录完后需要再转回来 假装什么都没发生
}
}
}
int main(){
ios::sync_with_stdio(false);
pre();
char input;
while(cin>>input){//由于输入间存在好几个空格,所以如果用scanf注意输入格式
string v="";
v=v+input;
for(short i=1;i<=8;i++){
cin>>input;
v=v+input;
}
if(m[v]==""&&v!="12345678x")//由于给出状态即是最终态所以没有步数,需要与无解的情况分别一下
cout<<"unsolvable"<<endl;
else
cout<<m[v]<<endl;
}
return 0;
}
这个差点爆炸,不过挑战上面广搜的一道练习题可以练习这一部分:Aizu - 0121(AOJ) 七数码问题
解法和上面的思路完全一样
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int MAX_N=100000;
const int INF = 0x3f3f3f3f;
#define eps 1e-7
map<string , int > m;
int dir[4]={-4,4,-1,1};
void bfs(){
queue<string> q;
m["01234567"]=1;
q.push("01234567");
while(!q.empty()){
string t=q.front();
q.pop();
int pi;
for(int i=0;i<8;i++){
if(t[i]=='0'){
pi=i;
break;
}
}
for(int i=0;i<4;i++){
if(pi==4&&i==2)
continue;
if(pi==3&&i==3)
continue;
int np=pi+dir[i];
if(np<0||np>=8)
continue;
string st=t;
swap(st[pi],st[np]);
if(m[st] == 0){
m[st]=m[t]+1;
q.push(st);
}
}
}
}
int main(){
ios::sync_with_stdio(false);
bfs();
string s;
char te;
while(cin>>te){
s=s+te;
for(int i=1;i<=7;i++){
cin>>te;
s=s+te;
}
// cout<<s<<endl;
cout<<m[s]-1<<endl;
s.clear();
}
return 0;
}
时间在2ms(大概数据很少),内存5440kb,这个还是能接受的
境界2:广搜+哈希
首先需要了解一下逆序数
在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。一个排列中所有逆序总数叫做这个排列的逆序数。也就是说,对于n个不同的元素,先规定各元素之间有一个标准次序(例如n个 不同的自然数,可规定从小到大为标准次序),于是在这n个元素的任一排列中,当某两个元素的先后次序与标准次序不同时,就说有1个逆序。一个排列中所有逆序总数叫做这个排列的逆序数。
逆序数为偶数的排列称为偶排列;逆序数为奇数的排列称为奇排列。
来源_百度百科
这道题里面我们可以用到奇偶排列不会相互转换判断是否有解
int sum=0;
for(int i=0;t[i];i++){
if(t[i]=='x')
continue;
for(int j=0;j<i;j++){
if(t[j]=='x')
continue;
if(t[i]<t[j])
sum++;
}
}
if(sum%2==1){
cout<<"unsolvable"<<endl;
continue;
}
然后就是我们可以自己写一个hash来判重
怎么构造hash函数呢?
就是利用康托展开和逆序数进行构造,这一部分在这里略过
代码实现:
首先需要写一个将字符串转成对应数字的函数
int getnum(){
int res=0;
for(int i=0;t[i];i++)
for(int j=i+1;t[j];j++)
if(t[j]<t[i])//x代表的是9大于所有其他的数 对于ascii表来说 x也大于任何数字 所以直接判断
res=res+c[8-i];//c是阶乘
return res;
}
其次还要写一个转回来的函数
void getstr(int val){
int temp[10];
int flag[10];
memset(flag,0,sizeof(flag));
for(int i=0;i<9;i++){
temp[i]=val/c[8-i];//删除阶乘
val=val%c[8-i];
}//得到每一个的逆序数
for(int i=0;i<9;i++){
int num=0;
for(int j=0;j<9;j++){
if(flag[j]==0)//未出现则++
num++;
if(num==temp[i]+1){
t[i]=j+'0'+1;
if(t[i]=='9')
t[i]=='x';
flag[j]=1;//标记该数已出现
break;
}
}
}
}
结构:
char t[1000];
int c[10];
int dy[4]={-1,1,0,0};
int dx[4]={0,0,-1,1};
char dir[4]={'u','d','l','r'};
stack<int> s;
struct Node{
int s;
int p;
Node(){}
Node(int S,int P){
s=S;
p=P;
}
};
struct path{//由于string保存缓慢,所以采用这种类似于链式的结构存储移动路径
int from;
int dir;
}pa[400000];
bool f[400000];
完整代码如下:
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<iomanip>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int MAX_N=100000;
const int INF = 0x3f3f3f3f;
#define eps 1e-7
char t[10];//存放棋盘
int c[10];//阶乘
int dy[4]={-1,1,0,0};
int dx[4]={0,0,-1,1};
char dir[4]={'u','d','l','r'};
stack<int> s;//存放路径
struct Node{
int s;
int p;
Node(){}
Node(int S,int P){
s=S;
p=P;
}
};
struct path{
int from;//链式
int dir;
}pa[400000];
bool f[400000];
int getnum(){//计算hash值
int res=0;
for(int i=0;t[i];i++)
for(int j=i+1;t[j];j++)
if(t[j]<t[i])
res=res+c[8-i];
return res;
}
void getstr(int val){//hash值转棋盘
int temp[10];
int flag[10];
memset(flag,0,sizeof(flag));
for(int i=0;i<9;i++){
temp[i]=val/c[8-i];
val=val%c[8-i];
}
for(int i=0;i<9;i++){
int num=0;
for(int j=0;j<9;j++){
if(flag[j]==0)
num++;
if(num==temp[i]+1){
t[i]=j+'0'+1;
if(t[i]=='9')
t[i]=='x';
flag[j]=1;
break;
}
}
}
}
void bfs(int val,int pos){//广搜
queue<Node> q;
q.push(Node(val,pos));
f[val]=1;
pa[val].from=-1;
pa[val].dir=-1;
while(!q.empty()){
Node h=q.front();
q.pop();
if(h.s==0){//如果找到了最终结果(最终结果的hash值为0)
int now=h.s;
while(1){
if(pa[now].from==-1)
break;
s.push(pa[now].dir);
now=pa[now].from;
}
break;
}
int a=h.p/3;//模拟棋盘转换
int b=h.p%3;
getstr(h.s);
for(int i=0;i<4;i++){
int y=a+dy[i];
int x=b+dx[i];
if(x<0||y<0||x>2||y>2)
continue;
int newpos=3*y+x;
swap(t[newpos],t[h.p]);
int news=getnum();
if(!f[news]){
pa[news].from=h.s;
pa[news].dir=i;
f[news]=1;
q.push(Node(news,newpos));
}
swap(t[newpos],t[h.p]);
}
}
}
int main(){
ios::sync_with_stdio(false);
c[0]=1;//计算阶乘
for(int i=1;i<=8;i++)
c[i]=c[i-1]*i;
char op;
while(cin>>op){
t[0]=op;
int pos;
for(int i=1;i<=8;i++){
cin>>op;
t[i]=op;
if(t[i]=='x')
pos=i;
}
int state=getnum();
int sum=0;
for(int i=0;t[i];i++){
if(t[i]=='x')
continue;
for(int j=0;j<i;j++){
if(t[j]=='x')
continue;
if(t[i]<t[j])
sum++;
}
}
if(sum%2==1){
cout<<"unsolvable"<<endl;
continue;
}//计算是否能通过变换转移到最终状态
memset(f,0,sizeof(f));
bfs(state,pos);
while(!s.empty()){
cout<<dir[s.top()];
s.pop();
}
cout<<endl;
}
return 0;
}
第三重境界:广搜+哈希+打表
第二重境界中poj1077已经可以在指定时间内求解,而hdu之所以不行是因为hdu是多组输入,那为了应对这种情况我们可以和境界1类似从终态打表
都挺类似的,直接扔代码
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<iomanip>
//cout<<setprecision(1)<<fixed<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int MAX_N=100000;
const int INF = 0x3f3f3f3f;
#define eps 1e-7
char t[9];
int c[10];
int dy[4]= {-1,1,0,0};
int dx[4]= {0,0,-1,1};
char dir[4]= {'d','u','r','l'};
stack<int> s;
struct node {
int s;
int p;
node(){}
node(int S,int P){s=S,p=P;}
};
struct way {
int from;
int dir;
} way[400000];
bool f[400000];
int getnum() {
int res=0;
for(int i=0; t[i]; i++)
for(int j=i+1; t[j]; j++)
if(t[j]<t[i])
res=res+c[8-i];
return res;
}
void getstr(int val) {
int temp[10];
int flag[10];
memset(flag,0,sizeof(flag));
for(int i=0; i<9; i++) {
temp[i]=val/c[8-i];
val=val%c[8-i];
}
for(int i=0; i<9; i++) {
int num=0;
for(int j=0; j<9; j++) {
if(flag[j]==0)
num++;
if(num==temp[i]+1) {
t[i]=j+'0'+1;
if(t[i]=='9')
t[i]='x';
flag[j]=1;
break;
}
}
}
}
void pre() {
queue<node> q;
q.push(node(0,8));//起始状态的hash值为0,x在最后一个位置
f[0]=1;
way[0].from=-1;
way[0].dir=-1;
while(!q.empty()) {
node h=q.front();
q.pop();
int a=h.p/3;
int b=h.p%3;
getstr(h.s);
for(int i=0; i<4; i++) {
int y=a+dy[i];
int x=b+dx[i];
if(y<0||y>2||x<0||x>2)
continue;
int newpos=3*y+x;
swap(t[newpos],t[h.p]);
int news=getnum();
if(!f[news]) {
q.push(node(news,newpos));
way[news].from=h.s;
way[news].dir=i;
f[news]=1;
}
swap(t[newpos],t[h.p]);
}
}
}
int main() {
ios::sync_with_stdio(false);
c[0]=1;
for(int i=1;i<=8;i++)
c[i]=c[i-1]*i;
pre();
while(cin>>t[0]){
for(int i=1;i<=8;i++)
cin>>t[i];
int state=getnum();//由于已经提前打了表,所以不用根据逆序数判断,直接就能判断
if(f[state]==0)
cout<<"unsolvable"<<endl;
else{
while(1) {
if(way[state].from==-1)
break;
cout<<dir[way[state].dir];
state=way[state].from;
}
cout<<endl;
}
}
return 0;
}
第四重境界:双向广搜+hash
简单来说双向广搜就是从开始和终态两个方向同时搜索,当两部分相互接触时,则产生的连同就是最短路,这样做可以使一些不必要的状态不被遍历到从而减少时间
点此查看图解
此链接中由一个匿名用户的回答的图解很详细的恢复了这个问题
这里有一个会变化的地方就是记录路径不再是一部分了,而是正向搜索的一部分和反向搜索的部分两者路径的加和,可以想象正向搜索是先进先出,而反向搜索的是先进后出,所以使用一个queue来记录正向搜索的路径,使用一个stack记录反向搜索的路径,在找到最短路径后,将路径押入 然后输出即可
stack<int> S;
queue<int> Q;
queue<node> q[3];
还有一个小优化就是为了保证双向广搜的效果,我们可以增加一个变量来记录两个状态目前扩展了多少顶点,为了保证双向广搜的效果最大,我们必然想要两个部分扩展的顶点尽可能接近。所以我们优先扩展顶点数少的一方向来平衡,在hdu题中,这种优化大概优化了200ms左右。
int cal[3];
其他部分都差不多比如hash需要一个状态转hash值的函数还有hash值转状态的函数
getnum();
getstr();
其他类似,就不详细讲了,代码如下:(以后的我应该能理清楚自己的思路…..吧)
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<set>
#include<bitset>
#include<iomanip>
//cout<<setprecision(1)<<fixed<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int MAX_N=100000;
const int INF = 0x3f3f3f3f;
#define eps 1e-7
char input[10];
char t[10];
int c[10];
struct node{
int s;
int p;
node(){}
node(int S,int P){
s=S;
p=P;
}
};
struct Path{
int from;
int dir;
}path[400000];
int f[400000];
int dx[4]={0,0,-1,1};
int dy[4]={-1,1,0,0};
string d[3]={"","udlr","durl"};
stack<int> S;
queue<int> Q;
queue<node> q[3];
int ans;
int getnum(){
int res=0;
for(int i=0;t[i];i++){
for(int j=i+1;t[j];j++){
if(t[j]<t[i])
res=res+c[8-i];
}
}
return res;
}
void getstr(int val){
int tmp[10],flag[10];
memset(flag,0,sizeof(flag));
for(int i=0;i<9;i++){
tmp[i]=val/c[8-i];
val=val%c[8-i];
}
for(int i=0;i<9;i++){
int num=0;
for(int j=0;j<9;j++){
if(flag[j]==0)
num++;
if(num==tmp[i]+1){
t[i]=j+'0'+1;
if(t[i]=='9')
t[i]=='x';
flag[j]=1;
break;
}
}
}
}
bool g(int a,int b){
if(a<0||b<0||a>2||b>2)
return false;
return true;
}
void bfs(int now){
node h=q[now].front();
q[now].pop();
int a=h.p/3;
int b=h.p%3;
getstr(h.s);
for(int i=0;i<4;i++){
int y=a+dy[i];
int x=b+dx[i];
if(!g(y,x))
continue;
int pos=3*y+x;
swap(t[h.p],t[pos]);
int te=getnum();
if(f[te]!=now){
if(f[te]==0){
f[te]=now;
path[te].from=h.s;
path[te].dir=i;
q[now].push(node(te,pos));
}
else{
ans=1;
if(now==1){
S.push(i);
int u=h.s;
while(path[u].from!=-1){
S.push(path[u].dir);
u=path[u].from;
}
u=te;
while(path[u].from!=-1){
Q.push(path[u].dir);
u=path[u].from;
}
}
else{
Q.push(i);
int u=h.s;
while(path[u].from!=-1){
Q.push(path[u].dir);
u=path[u].from;
}
u=te;
while(path[u].from!=-1) {
S.push(path[u].dir);
u=path[u].from;
}
}
break;
}
}
swap(t[h.p],t[pos]);
}
}
void init(){
memset(f,0,sizeof(f));
ans=0;
while(!q[1].empty())
q[1].pop();
while(!q[2].empty())
q[2].pop();
}
void work(int s,int pos){
q[1].push(node(s,pos));
q[2].push(node(0,8));
f[s]=1;
path[s].from=path[s].dir=-1;
f[0]=2;
path[0].from=path[0].dir=-1;
while((!q[1].empty())&&(!q[2].empty())){
if(ans==1)
break;
bfs(1);
if(ans==1)
break;
bfs(2);
if(ans==1)
break;
}
}
int main(){
ios::sync_with_stdio(false);
c[0]=1;
for(int i=1;i<=8;i++)
c[i]=c[i-1]*i;
while(cin>>t[0]){
for(int i=1;i<=8;i++)
cin>>t[i];
for(int i=0;i<=9;i++)
input[i]=t[i];
int sum=0;
for(int i=0;t[i];i++){
if(t[i]=='x')
continue;
for(int j=0;j<i;j++){
if(t[j]=='x')
continue;
if(t[i]<t[j])
sum++;
}
}
if(sum%2==1){
cout<<"unsolvable"<<endl;
continue;
}
init();
for(int i=0;i<9;i++){
if(input[i]=='x'){
work(getnum(),i);
break;
}
}
if(ans==1){
while(!S.empty()){
cout<<d[1][S.top()];
S.pop();
}
while(!Q.empty()){
cout<<d[2][Q.front()];
Q.pop();
}
cout<<endl;
}
else
cout<<"unsolvable"<<endl;
}
return 0;
}
之后四个境界大概要过段时间了,因为我虽然理解了这个问题的估价函数(使用曼哈顿距离),但是却无法类推其他估价函数,比如如果换一种题型,那么如何构造出来估价函数就不懂了,大概是能力不到吧,暂时先空吧..