八数码问题(八重境界)

参考
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;
}

之后四个境界大概要过段时间了,因为我虽然理解了这个问题的估价函数(使用曼哈顿距离),但是却无法类推其他估价函数,比如如果换一种题型,那么如何构造出来估价函数就不懂了,大概是能力不到吧,暂时先空吧..

猜你喜欢

转载自blog.csdn.net/qq_38842456/article/details/80149706