A* \IDA* 分析总结

   

经典八皇后问题:

 

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstring>
using namespace std;

struct node{
    int tab[3][3];
    int r,c;
    int hash_val;    //当前排列在全排列时候的大小位置

    node* pre;
    int op;
    int f,g;         //f:估计值 g:从起始点到当前点的花费

    bool operator < (const node& rhs)const{
         return f > rhs.f;
    }
}st,ed;

node t[370000];
int tot;
int hash_tab[370000];
priority_queue<node> open;
int fn[10];
int ed_map[10][2];
int dir_i[4] = {0,0,1,-1};
int dir_j[4] = {1,-1,0,0};
char print_op[4] = {'r','l','d','u'};

//处理输入的字符
void In(char s[],node& t){
    char ch;
    int next = 0;
    for(int i = 0;i < 3;++i){
        for(int j = 0;j < 3;++j){
            while(s[next] == ' ') ++next;
            ch = s[next++];
            if(isdigit(ch))
                st.tab[i][j] = ch - '0';
            else {          // x 的位置
                st.r = i;
                st.c = j;
                st.tab[i][j] = 0;
            }
        }
    }
}


//获取估价函数值
int get_f(node a,int g){
    int h = 0;
    for(int i = 0;i < 3;++i)
        for(int j = 0;j < 3;++j)
           if(a.tab[i][j])     //距离目标点的曼哈顿距离
              h += abs(i - ed_map[a.tab[i][j]][0]) + abs(j - ed_map[a.tab[i][j]][1]);
    return g + h;
}



//康拓展开,判断该组合在所有排列中的位置
int get_hash(node a){
    int ret;
    ret = 0;
    int num = 8;
    for(int i = 0;i < 3;++i){
        for(int j = 0;j < 3;++j){
            int x = 0;

            //当前这个数后有多少个比他小
            for(int jj = j + 1;jj < 3;++jj)
                if(a.tab[i][jj] < a.tab[i][j]) x++;
            for(int ii = i + 1;ii < 3;++ii)
                for(int jj = 0;jj < 3;++jj)
                   if(a.tab[ii][jj] < a.tab[i][j]) x++;
            ret += fn[num] * x;             //康拓展开
            num--;
        }
    }
    return ret;
}


//预处理
void init(){
     memset(hash_tab,0,sizeof(hash_tab));

     while(!open.empty()) open.pop();

     tot = 0;

     st.f = get_f(st,0);       //获取估价函数值
     st.g = 0;
     st.hash_val = get_hash(st);  //获取该组合在全排列的位置

     open.push(st);
     hash_tab[st.hash_val] = 1;    //当前的组合排列已经遍历过了
}

void pre(){
    //预处理阶乘
   fn[0] = 1;
   for(int i = 1;i < 9;++i) fn[i] = i * fn[i - 1];

   //预处理结果
   for(int i = 0;i < 3;++i)
      for(int j = 0;j < 3;++j)
         ed.tab[i][j] = (i * 3) + j + 1;
   ed.tab[2][2] = 0;
   ed.hash_val = get_hash(ed);

   //预处理结果位置的映射
   for(int i = 0;i < 3;++i)
     for(int j = 0;j < 3;++j)
        if(ed.tab[i][j]){
            ed_map[ed.tab[i][j]][0] = i;
            ed_map[ed.tab[i][j]][1] = j;
        }
}


//判断逆序数的奇偶性
int get_preval(node a){
   int ret = 0;

   for(int i = 0;i < 3;++i)
     for(int j = 0;j < 3;++j){
        if(!a.tab[i][j]) continue;

        int x = 0;
        for(int jj = j + 1;jj < 3;++jj)
            if(a.tab[i][jj] && a.tab[i][jj] < a.tab[i][j]) x++;

        for(int ii = i + 1;ii < 3;++ii)
            for(int jj = 0;jj < 3;++jj)
               if(a.tab[ii][jj] && a.tab[ii][jj] < a.tab[i][j]) x++;

        ret += x;
     }

     return ret & 1;
}

bool pre_solve(){
    return(get_preval(st)^(get_preval(ed)));
}

void change(node &tmp,node a,int nextr,int nextc,int _i,int idx){
    for(int i = 0;i < 3;++i)
        for(int j = 0;j < 3;++j)
           tmp.tab[i][j] = a.tab[i][j];

    swap(tmp.tab[nextr][nextc],tmp.tab[a.r][a.c]);
    tmp.hash_val = get_hash(tmp);

    tmp.r = nextr;
    tmp.c = nextc;
    tmp.pre = &t[idx];
    tmp.op = _i;              //移动的方向
    tmp.g = a.g + 1;          //当前的步数
    tmp.f = get_f(tmp,tmp.g);
}

bool check(int i,int j){
    if(i > 2 || i < 0 || j > 2 || j < 0) return false;
    return true;
}

//打印路径
void path(node* a){
    if(a->hash_val == st.hash_val) return ;
    path(a->pre);
    printf("%c",print_op[a->op]);
}

//A*
void Astar(){
    int nextr,nextc;
    node ans = st;
    int fla = 0;

    if(st.hash_val != ed.hash_val)
    while(!open.empty()){
        node a = open.top();
        open.pop();
        t[++tot] = a;

        for(int i = 0;i < 4;++i){
            nextr = a.r + dir_i[i];
            nextc = a.c + dir_j[i];

            if(check(nextr,nextc)){
                node tmp;
                change(tmp,a,nextr,nextc,i,tot);

                if(hash_tab[tmp.hash_val]) continue;
                if(tmp.hash_val == ed.hash_val){
                    fla = 1;
                    ans = tmp;
                    break;
                }
                open.push(tmp);
            }
        }
        if(fla) break;
        hash_tab[a.hash_val] = 1;
    }
    path(&ans);
    puts("");
}

int main()
{

   // freopen("Input.txt","r",stdin);

    char ss[12];
    while(gets(ss)){
        pre();
        In(ss,st);
        init();
        if(pre_solve())
            puts("unsolvable");
        else
            Astar();
    }
    return 0;
}









 

 

 

对于空格(0)的左移/右移操作,对应序列不变(逆序数也就不变) 
对于空格(0)的上移/下移操作,相当于序列的某个数字前移/后移两位,该序列的逆序数奇偶性不变。 
所以求初始状态与目标状态的逆序数可作出判断 
例中前者为奇,后者为偶,因此无解

利用奇偶性判断所给出的初始状态有无解. 

判别方法是: 
以数组为一维的举例子. 
将八数码的一个结点表示成一个数组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同为奇数或者同为偶数,那么该状态有解,否则无解。 

考虑到四种移动方法对sigma(p(x))的影响,左移和右移是不会影响它的值的, 
更不会影响奇偶性,如果是上移或者下移就会影响: 
上移:一次上移会使一个元素向前跳两个数字的位置,设这两个数字为a1,a2, 
不妨设a1<a2,移的这个数字设为a0,那无非只有以下三次情况: 
1,a0<a1<a2,考虑它们三者的p(x)值,p(a0)不变,p(a1)++,p(a2)++,总体增加了2 
2,a1<a0<a2,p(a0)--,p(a1)不变,p(a2)++,总体不变 
3,a1<a2<a0,p(a0)-=2,p(a1),p(a2)不变,总体减小了2 

综合起来的结论就是不会影响sigma(p(x))的奇偶性。

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值