POJ 1077 Eight A*算法 IDA*算法 康拓展开

17 篇文章 0 订阅
7 篇文章 0 订阅

这道题是一道经典的搜索题

第一次做A*算法的题目

这道题需要知道的,第一是怎样把全排列转换为数字,第二是h函数的设计

全排列转化为数字用到的是康托展开,跟逆序数有关,这是一个比较经典的转换。转换成数字的目的就是为了状态的判重,所以A*算法的空间需求总是指数级的

h函数用的是曼哈顿距离的总和,然后f=g+h,g函数就是从初始状态到现在状态花费的步骤数了。

然后使用优先队列,进行BFS。

这道题目,POJ上的数据还是很弱的。所以一般都0ms就水过了,普通的BFS也能很快搜过去。

貌似这道题目是不存在无解的情况,判别的方法其实也有。

判别方法是:
以数组为一维的举例子.
将八数码的一个结点表示成一个数组a[9],空格用0表示,设临时函数p(x)定义为:x数所在位置前面的数比x小的数的个数,
其中0空格不算在之内,那设目标状态为b[9],那r=sigma(p(x)) sigma()表示取所有的x:1-8并求和,
那对于初始状态a[9],t=sigma(p(x)),如果r和t同为奇数或者同为偶数,那么该状态有解,否则无解。

/*
ID: sdj22251
PROG: inflate
LANG: C++
*/
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <cstring>
#include <cmath>
#include <ctime>
#define MAXN 105
#define INF 1000000000
using namespace std;
struct wwj
{
    int pre, step, dir, d, pos, id;
    char x[12];
    bool operator <(const wwj &a) const
    {
        return a.d < d;
    }
    wwj(){dir = -1;}
    wwj(int u, int v, int w, int y, char t[])
    {
        pre = u;
        step = v;
        dir = w;
        pos = y;
        strcpy(x, t);
    }
}p[400000];
int jc[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};
int d[] = {-1, -3, 1, 3};
int v[400000];
char ans[]= "12345678x";
bool move[][4] = {{0,0,1,1}, {1,0,1,1}, {1,0,0,1}, {0,1,1,1}, {1,1,1,1}, {1,1,0,1}, {0,1,1,0}, {1,1,1,0}, {1,1,0,0}};
int hash(wwj &t)  //将全排列转化为数字,序列值为0~n!-1,达到了完美的映射
{
    int h = 0;
    for(int i = 1; i < 9; i++)
    {
        int count = 0;
        for(int j = 0; j <i ; j++)
            if(t.x[j] > t.x[i]) count++;
        h += count * jc[i];
    }
    return h;
}
int h(wwj &t)
{
    int sum = 0;
    for(int i = 0; i < 3; i++)
        for(int j = 0; j < 3; j++)
        {
            int k = i * 3 + j;
            if(t.x[k] == 'x') continue;
            sum += abs(i - (t.x[k] - '1') / 3) + abs(j - (t.x[k] - '1') % 3);
        }
    return sum;
}
void shuchu(wwj t)
{
    char res[5000];
    int cnt = 0;
    while(t.pre != -1)
    {
        //printf("%s\n", t.x);
        if(t.dir == 0)
        res[cnt++] = 'l';
        else if(t.dir == 1)
        res[cnt++] = 'u';
        else if(t.dir == 2)
        res[cnt++] = 'r';
        else if(t.dir == 3)
        res[cnt++] = 'd';
        t = p[t.pre];
    }
    res[cnt] = '\0';
    reverse(res, res + cnt);
    printf("%s\n", res);
}
priority_queue<wwj>q;
int main()
{
    wwj tmp;
    for(int i = 0; i < 9; i++)
    {
        scanf("%s", &tmp.x[i]);
        if(tmp.x[i] == 'x') tmp.pos = i;
    }
    tmp.step = 0;
    tmp.pre = -1;
    tmp.d = tmp.step + h(tmp);
    tmp.id = hash(tmp);
    p[tmp.id] = tmp;
    q.push(tmp);
    v[tmp.id] = 1;
    while(!q.empty())
    {
        wwj A = q.top();
        q.pop();
        if(strcmp(A.x, ans) == 0) {shuchu(A); break;}
        for(int i = 0; i < 4; i++)
        {
            if(move[A.pos][i] == 0) continue;
            int xx = A.pos + d[i];
            if(xx < 0 || xx >= 9) continue;
            char tx[12];
            strcpy(tx, A.x);
            swap(tx[xx], tx[A.pos]);
            wwj B(A.id, A.step + 1, i, xx, tx);
            B.id = hash(B);
            if(v[B.id]) continue;
            v[B.id] = 1;
            B.d = h(B) + B.step;
            p[B.id] = B;
            q.push(B);
        }
    }
    return 0;
}

然后是IDA*算法,IDA*是基于A*的迭代加深搜索,其特点是忽略大于某些深度的状态。如果有解,直接输出,无解的话,放宽松深度限制,继续搜索。

对比A*算法,IDA*无疑是优点巨大的,因为其不需要判重,所以就不需要存储状态,也就节省了巨大的空间。

我的理解就是,设置一个h函数,就是曼哈顿距离的总和,目标状态的h函数值必然是0,则八数码的解决过程总体来说应该是一个h值逐渐变小的过程,所以过程中一些大于这个h值的状态就不搜索了。如果说在这个限制下无解,那么就要放宽h值的限制,中途中可以适当的向一些较大的h值的状态搜索,这就要看,之前最多搜到了哪一步。然后把限制对应的进行修改。

/*
ID: sdj22251
PROG: inflate
LANG: C++
*/
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <cstring>
#include <cmath>
#include <ctime>
#define MAXN 105
#define INF 1000000000
using namespace std;
struct wwj
{
    int pos;
    char x[12];
}tmp;
int top, found, st[400000], mi;
int d[] = {-1, -3, 1, 3};
char as[] = "lurd";
bool move[][4] = {{0,0,1,1}, {1,0,1,1}, {1,0,0,1}, {0,1,1,1}, {1,1,1,1}, {1,1,0,1}, {0,1,1,0}, {1,1,1,0}, {1,1,0,0}};
int h(wwj &t)
{
    int sum = 0;
    for(int i = 0; i < 3; i++)
        for(int j = 0; j < 3; j++)
        {
            int k = i * 3 + j;
            if(t.x[k] == 'x') continue;
            sum += abs(i - (t.x[k] - '1') / 3) + abs(j - (t.x[k] - '1') % 3);
        }
    return sum;
}
void output()
{
    for(int i = 1;i <= top; i++) printf("%c", as[st[i]]);
    printf("\n");
}
void dfs(int p, int dp, int maxdp)
{
    int f = h(tmp);
    int i, t;
    if(mi > f) mi = f;
    if(f + dp > maxdp || found) return;
    if(f == 0)
    {
        output();
        found = 1;
        return;
    }
    for(i = 0;i < 4; i++)
    {
        if(move[p][i] == 0) continue;
        t = d[i] + p;
        if(top > 0 && d[st[top]] + d[i] == 0) continue;  //一个重要的剪枝,如果之前的一步和现在的一步是相反的,无疑是做了无用功,不必搜索。
        swap(tmp.x[t], tmp.x[p]);
        st[++top] = i;
        dfs(t, dp + 1, maxdp);
        top--;
        swap(tmp.x[t], tmp.x[p]);
    }
}
void IDA()
{
    int maxdp = h(tmp);//初始为初状态的h值
    found = 0;
    while(found == 0)
    {
        mi = 0x7fffffff; //mi表示在当前的h值限制下所能搜到的最优位置,即h值最小的位置
        top = 0;
        dfs(tmp.pos, 0, maxdp);
        maxdp += mi; //如果不能搜到就将mi加上去
    }
}
int main()
{
    for(int i = 0; i < 9; i++)
    {
        scanf("%s", &tmp.x[i]);
        if(tmp.x[i] == 'x') tmp.pos = i;
    }
    IDA();
    return 0;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值