关闭

POJ 1077 八数码 三种解法

标签: poj搜索
718人阅读 评论(0) 收藏 举报
分类:

POJ 1077 Eight

题目链接:http://poj.org/problem?id=1077

有人说做搜索不做这道题,人生遗憾。我做完了之后,嗯,同意。

关于八数码问题,有以下几个问题需要想明白:

【状态表示和状态判断】
题目是按照连续9个字符的形式读入的,自然想到在node中用一个s[9]的数组存储当前状态。但是可以马上否认的是,我们不可能也用这个字符串来进行状态之间的比较,因为效率太低,操作麻烦,实现费劲。这个字符串一共有9!中排列方式,这里有一个定理:康托展开。很好地解决了存储和比较两个问题。通过康托展开,就可以把这9!个数字分别转换为一个int(注:9! = 362880 < 2^31-1)

【无解状态剪枝】
我们要的最后一个状态是{1, 2, 3, 4, 5, 6, 7, 8, x}, 这个排列的逆序数是0,是偶数。而对于任意一个排列,显然,因为x不表示数字,那么也就是说把x向左右两个方向移动并不会改变整个序列的逆序数,那么有结论:
如果初始序列的逆序数是一个奇数,则必然这个序列无解(必要,不充分)

有了以上两个想法,下一步就是选择搜索策略:
bfs可以,A*可以,IDA*也可以

【参考代码】

/**
 * Name: Eight 
 * P_ID: POJ 1077
 * Note: BFS + 康托展开
 * Date: 2016-05-18
 *
 */
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 3;
const int cell = 362880;


int vis[cell];
int parent[cell];
char step[cell];

//cantor Base
const int fac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
//dir
const int dir[4][2] = {{-1, 0}, {1,0}, {0, -1}, {0, 1}}; //u, d, l, r

struct node {
    char s[9];
    int space;
};

//康拓定理正向加密过程
int Hash(const char* str)
{
    int n = 9;
    int num = 0;
    int temp;
    for(int i=0; i<n-1; ++i)
    {
        temp=0;
        for(int j=i+1; j<n; j++)
        {
            if(str[j] < str[i])
                temp++;
        }
        num += fac[str[i]-1] * temp;
    }
    return num;
}

//康托定理逆向解码过程
void get_node(int num, node &temp)
{
    int n = 9;
    int a[9];
    for(int i=2; i<=n; ++i)
    {
        a[i-1] = num%i;
        num /= i;
        temp.s[i-1] = 0;
    }

    temp.s[0] = 0;
    int rn, i;
    for(int k=n; k>=2; --k)
    {
        rn = 0;
        for(i=n-1; i>=0; --i)
        {
            if(temp.s[i]!=0)
                continue;
            if(rn==a[k-1])
                break;
            ++rn;
        }
        temp.s[i] = k;
    }

    for(i=0; i<n; ++i)
    {
        if(temp.s[i]==0)
        {
            temp.s[i] = 1;
            break;
        }
    }
    temp.space = n - a[n-1] - 1;
}

//搜索
void bfs(const node& begin)
{
    memset(vis, 0, sizeof(vis));
    int u = Hash(begin.s);
    vis[u] = 1;
    parent[u] = -1;//便于后续输出路径,保存了每一步的父节点

    queue<int> myQue;
    myQue.push(u);

    node now, next;
    while(!myQue.empty())
    {
        u = myQue.front();
        myQue.pop();

        get_node(u, now);

        int k = now.space;
        int x = k/3;
        int y = k%3;

        for(int i=0; i<4; ++i)
        {
            int xx = x + dir[i][0];
            int yy = y + dir[i][1];
            if(xx>=0 && xx<=2 && yy>=0 && yy<=2)
            {
                next = now;
                next.space = xx * 3 + yy;
                swap(next.s[k], next.s[next.space]);//移动
                int v = Hash(next.s);
                if(!vis[v])
                {
                    step[v] = i;
                    vis[v] = 1;
                    parent[v] = u;
                    if(v==0)
                        return;//终止,到达了目标状态
                    myQue.push(v);
                }
            }
        }
    }
}

void print()
{
    int n, u;
    char path[1000];
    n = 1;
    path[0] = step[0];
    u = parent[0];
    while(parent[u]!=-1)
    {
        path[n] = step[u];
        ++n;
        u = parent[u];
    }

    for(int i=n-1; i>=0; --i)
    {
        if(path[i]==0)
            cout << "u";
        else if(path[i]==1)
            cout << "d";
        else if(path[i]==2)
            cout << "l";
        else
            cout << "r";
    }
}

int main()
{
    node start;
    char c;
    for(int i=0; i<9; ++i)
    {
        cin >> c;
        if(c=='x')
        {
            start.s[i] = 9;
            start.space = i;
        }
        else 
            start.s[i] = c - '0';
    }

    bfs(start);

    if(vis[0]==1)
        print();
    else 
        cout << "unsolvable";

    cout << endl;
    return 0;
}
/**
 * Name: Eight
 * P_ID: POJ 1077
 * Note: A star algorithm + cantor
 * date: 2016-05-19
 */ 
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 3;


const int cell = 362880;

//cantor Base 
int fac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};

int vis[cell];
int parent[cell];
char step[cell];

//dir
const int dir[][2] = {{-1, 0}, {1,  0}, {0, -1}, {0, 1}}; //u, d, l, r

//康托定理正向加密过程
int Hash(const char* str) 
{
    int n = 9;
    int num = 0;
    int temp;
    int i, j;
    for(i=0; i<n-1; ++i)
    {
        temp = 0;
        for(j=i+1; j<n; j++)
        {
            if(str[j] < str[i])
                temp++;
        }
        num += fac[str[i]-1] * temp;
    }
    return num;
}

struct node {
    char s[9];
    int space;
};


//康托定理逆向解密过程
void get_node(int num, node &temp)
{
    int n = 9;
    int a[9];
    for(int i=2; i<=n; ++i)
    {
        a[i-1] = num%i;
        num /= i;
        temp.s[i-1] = 0;
    }
    temp.s[0] = 0;
    int rn, i;
    for(int k=n; k>=2; --k)
    {
        rn = 0;
        for(i=n-1; i>=0; --i)
        {
            if(temp.s[i]!=0)
                continue;
            if(rn==a[k-1])
                break;
            ++rn;
        }
        temp.s[i] = k;
    }
    for(int i=0; i<n; ++i)
    {
        if(temp.s[i]==0)
        {
            temp.s[i] = 1;
            break;
        }
    }
    temp.space = n - a[n-1] - 1;
}

//d表示当前深度,从初始位置到现在走过的消耗
int f[cell], d[cell];
//各自的目标位置
int goal_state[9][2] = {
    {0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}, {2, 2}
};

//启发式搜索的估价函数
int h(const char* str) 
{
    int k;
    int hv = 0;
    for(int i=0; i<3; ++i)
    {
        for(int j=0; j<3; ++j)
        {
            k = i * 3 + j;
            if(str[k]!=9)
            {
                hv += abs(i - goal_state[str[k]-1][0]) + abs(j - goal_state[str[k]-1][1]);//欧几里得距离
            }
        }
    }
    return hv;
}


struct comp {
    bool operator () (int u, int v) {
        return f[u] > f[v];
    }
};

void A_star(const node &begin) 
{
    priority_queue<int, vector<int>, comp> myQue;//维护一个堆
    memset(vis, 0, sizeof(vis));
    int u = Hash(begin.s);
    vis[u] = 1;//加入开放列表
    parent[u] = -1;
    d[u] = 0;
    f[u] = h(begin.s);
    myQue.push(u);//置入初始节点
    node now, next;
    while(!myQue.empty())
    {
        u = myQue.top();
        myQue.pop();//弹出当前节点,加入关闭list中

        if(u==0) return;//如果是目标状态直接结束

        get_node(u, now);

        int k = now.space;
        int x = k/3;
        int y = k%3;

        //尝试扩展当前节点的所有子节点
        for(int i=0; i<4; ++i)
        {
            int xx = x + dir[i][0];
            int yy = y + dir[i][1];
            if(xx>=0 && xx<=2 && yy>=0 && yy<=2)
            {
                next = now;
                next.space = xx * 3 + yy;
                swap(next.s[k], next.s[next.space]);
                int v = Hash(next.s);

                //核心:vis[] = 1是一个开放list,myQue弹出来的元素是关闭列表。当搜索进行时,当检测到vis[] = 0的node,那么这是没有检测过的node,则计算相应的d、h、f值并把它加入到开放列表中去。所谓开放列表,就是需要再次进行检测的列表。

                if(vis[v]==1 && (d[u]+1) < d[v]) //对开放列表中的元素进行更新
                {
                    step[v] = i;
                    f[v] = f[v] - d[v] + d[u] + 1;
                    d[v] = d[u] + 1;
                    parent[v] = u;
                    myQue.push(v);//这个过程由于堆这种数据结构而得以很好地保持了顺序性
                }
                else if(vis[v]==0)
                {
                    step[v] = i;
                    d[v] = d[u] + 1;
                    f[v] = d[v] + h(next.s);
                    parent[v] = u;
                    myQue.push(v);
                    vis[v] = 1;//加入开放列表
                }
            }
        }
    }
}

void print()
{
    int n, u;
    char path[1000];
    n = 1;
    path[0] = step[0];
    u = parent[0];
    while(parent[u]!=-1)
    {
        path[n] = step[u];
        ++n;
        u = parent[u];
    }

    for(int i=n-1; i>=0; --i)
    {
        if(path[i]==0)
            cout << "u";
        else if(path[i]==1)
            cout << "d";
        else if(path[i]==2)
            cout << "l";
        else
            cout << "r";
    }
}


int main()
{
    node start;
    char c;
    for(int i=0; i<9; ++i)
    {
        cin >> c;
        if(c=='x')
        {
            start.s[i] = 9;
            start.space = i;
        }
        else start.s[i] = c - '0';
    }

    A_star(start);

    if(vis[0]!=0)
        print();
    else
        printf("unsolvable"); 

    cout << endl;

    return 0;
}
/**
 *
 * Name: Eight
 * P_ID: POJ 1077
 * Date: 2016-05-30
 * Note: IDA star + 八数码无解判定 + 剪枝
 *
 */
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 3;

struct node {
    char s[9];
    int space;
};


node start;
const int dir[][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; //u, d, l, r
int maxD;//最大搜索深度
int mov[1000];//记录路径


int cal_h() 
{
    int ans = 0;
    for(int i=0; i<9; ++i)
    {
        if(start.s[i]==9) continue;
        int x = (start.s[i]-1)/3;
        int y = (start.s[i]-1)%3;
        int nx = i/3;
        int ny = i%3;
        ans += abs(x-nx) + abs(y-ny);//曼哈顿距离
    }
    return ans;
}


bool dfs(int d, int last)
{
    if(d==maxD)//已到达最大搜索深度   经典结构
    {
        if(cal_h()==0) return true;//是否刚好正解
        return false;
    }
    for(int i=0; i<4; ++i)
    {
        int xx = start.space/3 + dir[i][0];
        int yy = start.space%3 + dir[i][1];
        int newP;
        if(xx<0 || xx>2 || yy<0 || yy >2) continue;
        else
            newP = xx*3 + yy;
        //移动
        swap(start.s[newP], start.s[start.space]);
        swap(start.space, newP);

        if(d+cal_h() > maxD)//剪枝,预判,前瞻性  经典结构
        {
            //回溯
            swap(start.s[newP], start.s[start.space]);
            swap(start.space, newP);
            continue;
        }

        mov[d+1] = i;
        if(dfs(d+1, i)) return true;//继续dfs

        //回溯  经典结构
        swap(start.s[newP], start.s[start.space]);
        swap(start.space, newP);
    }
    return false;
}

//IDA star驱动函数,经典结构
void IDA_Star() 
{
    maxD = 0;
    while(1)
    {
        if(dfs(0, 100)) return;
        maxD++;//直到满足最小的maxD
    }
}

bool Unsolve()
{
    int cnt = 0;
    for(int i=0; i<9; ++i)
    {
        if(start.s[i]==9) continue;
        for(int j=0; j<i; ++j) 
        {
            if(start.s[j]==9) continue;
            if(start.s[j] > start.s[i]) cnt++;
        }
    }
    return !(cnt%2);
}

void print(int dep)
{
    if(!dep) return;
    print(dep-1);
    if(!mov[dep]) printf("u");
    else if(mov[dep]==1) printf("l");
    else if(mov[dep]==2) printf("d");
    else printf("r");
}

int main()
{
    char c;
    for(int i=0; i<9; ++i)
    {
        cin >> c;
        if(c=='x')
        {
            start.s[i] = 9;
            start.space = i;
        }
        else start.s[i] = c - '0';
    }

    if(!Unsolve()) printf("unsolvable");//无解条件判断、剪枝
    else 
    {
        IDA_Star();
        print(maxD);
    }
    cout << endl;

    return 0;
}
1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:2638次
    • 积分:166
    • 等级:
    • 排名:千里之外
    • 原创:14篇
    • 转载:0篇
    • 译文:0篇
    • 评论:1条
    文章分类
    文章存档
    最新评论