A*

转自http://www.mamicode.com/info-detail-1534200.html

康托展开

X = a[1]*(n-1)!+a[2]*(n-2)!+...+a[i]*(n-i)!+...+a[n-1]*1!+a[n]*0!
其中a[i]表示在num[i+1..n]中比num[i]小的数的数量

逆康托展开

由于:

a[i]≤n-i, a[i]*(n-i)!≤(n-i)*(n-i)!<(n-i+1)!

于是我们得到:

对于X=a[1]*(n-1)!+a[2]*(n-2)!+...+a[i]*(n-i)!+...+a[n-1]*1!+a[n]*0!

中的a[i]来说,无论a[i+1..n]的值为多少,其后面的和都不会超过(n-i)!

eg:当i=1  所以X约等于a[1]*(n-1)! +后面

X除以(n-1)!,得到商c和余数r。其中c就等于a[1],r则等于后面的部分


这样依次求解,就可以得到a[]数组了!

比如求解3的全排列中,编号为3的排列:

3 / 2! = 1 ... 1 => a[1] = 1
1 / 1! = 1 ... 0 => a[2] = 1
0 / 0! = 0 ... 0 => a[3] = 0

之后我们回想一下a[i]和含义  

a[i]表示在num[i+1..n]中比num[i]小的数的数量

对于已经排好序的序列比如

 {1, 2, 3},

a[] = {1, 1, 0}
unused = {1, 2, 3}, a[1] = 1, num[1] = 2
unused = {1, 3}, a[2] = 1, num[2] = 3
unused = {1}, a[3] = 0, num[3] = 1
=> 2, 3, 1

解释看http://www.mamicode.com/info-detail-1534200.html 

讲得很好

代码也很易懂


#include <algorithm>
#include <cstring>
#include <string.h>
#include <iostream>
#include <list>
#include <map>
#include <set>
#include <stack>
#include <string>
#include <utility>
#include <queue>
#include <vector>
#include <cstdio>
#include <cmath>

#define LL long long
#define N 100005
#define INF 0x3ffffff

using namespace std;

int factor[]={1,1,2,6,24,120,720,5040,40320};
vector<int>stnum; //八数码某个状态序列
int destination; //目标状态康托展开值

struct state
{
    int f; //评估值和状态值的总和。
    int g; //从起始状态到当前状态的最少步数
    int h; //评估函数
    int k; //该状态康托展开值
    state(int f,int g,int h,int k):f(f),g(g),h(h),k(k){};
    friend bool operator<(state a,state b)
    {
        if(a.f!=b.f)
        return a.f>b.f;
        else return a.g>b.g;
    }
};

int cantor(vector<int>num) //康托展开
{
    int k = 0;
    int n = 9;
    for(int i=0;i<n;i++) {
        int tp = 0;
        for(int j = i+1; j < n; j++) {
            if(num[j] < num[i]) {
                tp++;
            }
        }
        k += tp * factor[n-1-i];
    }
    return k;
}

vector<int> recantor(int k) //逆康托展开
{
    vector<int>num;
    int a[10];
    int n = 9;
    bool used[10];
    memset(used,false,sizeof(used));
    for(int i=0;i<n;i++){
        a[i] = k / factor[n-1-i];
        k %= factor[n-1-i];
        int cnt = 0;
        for(int j=0;j<n;j++){
            if(!used[j]) {
                cnt++;
                if(a[i] + 1==cnt) {
                    num.push_back(j);
                    used[j] = true;
                    break;
                }
            }
        }
    }
    return num;
}


int pos[]={8,0,1,2,3,4,5,6,7};


int getdis(int a,int b) //曼哈顿距离
{
    return (abs(a/3-b/3)+abs(a%3-b%3));//a/3-b/3行差,a%3-b%3列差
}

int get_evaluation(vector<int>num) //评估函数
{
    //评估函数设定为1-8八数字当前位置到目标位置的曼哈顿距离之和。 比如其中就有当前位置的1和目标位置的1 的曼哈顿距离之和
    //目标式1 2 3 4 5 6 7 8 0
    //此时 pos[1]=0 ,表示在目标式中1原本应该在第0位 从而推出0应该在第8位 对应pos[0] 所以pos的作用是 映射这个数字在目标式子中的位置,所以要偏移
    //当前   2 4 7 6 8 0 1 3 5
    //因此 i 表示 在当前式子中这个数字的位置  比如2 是第0位 4 是第1位
    int h=0;
    for(int i=;i<9;i++)
        {
            h+=getdis(i,pos[num[i]]);
        }
    return h;
}

void solve()
{
    priority_queue<state>openlist;
    set<int>closelist; //在closelist中,抛弃该状态。
    while(!openlist.empty()) openlist.pop();
     closelist.clear();
    int h=get_evaluation(stnum);
    openlist.push(state(h,0,h,cantor(stnum)));
    int step=0; //统计步数
    bool flag=false; //标记是否能找到目标状态
    while(!openlist.empty())
        {
            state cur=openlist.top(); //从openlist中取出F值最小的状态
            openlist.pop();
            int k=cur.k;//cur的康托展开 
            closelist.insert(k); //将该状态加入closelist 
            if(destination==k) {//若该状态为目标状态,结束搜索
                flag=true; //找到目标状态
                step=cur.g; //步数
                break;
            }
            vector<int> st = recantor(k); //当前状态的八数码序列
            for(int i=0;i<9;i++) if(st[i]==0) //找到0的位置
                {
                    if(i%3!=2) { //不在行末,0位可以和右边相邻位置交换
                    swap(st[i],st[i+1]);
                    int x = cantor(st);
                    int g = cur.g+1;
                    int h = get_evaluation(st);
                    int f = g + h;
                    if(closelist.find(x)==closelist.end()) {
                        openlist.push(state(f,g,h,x));
                        }
                    swap(st[i],st[i+1]);//又要换回来来进行下面的步骤
                    }
                    if(i%3!=0) { //不在某行开头,可以和左边相邻位置交换
                    swap(st[i],st[i-1]);
                    int x = cantor(st);
                    int g = cur.g+1;
                    int h = get_evaluation(st);
                    int f = g + h;
                    if(closelist.find(x)==closelist.end()) {
                        openlist.push(state(f,g,h,x));
                        }
                    swap(st[i],st[i-1]);
                    }
                    if(i<6) {
                    swap(st[i],st[i+3]);
                    int x = cantor(st);
                    int g = cur.g+1;
                    int h = get_evaluation(st);
                    int f = g + h;
                    if(closelist.find(x)==closelist.end()) {
                        openlist.push(state(f,g,h,x));
                        }
                    swap(st[i],st[i+3]);
                    }
                    if(i>2) {
                    swap(st[i],st[i-3]);
                    int x = cantor(st);
                    int g = cur.g+1;
                    int h = get_evaluation(st);
                    int f = g + h;
                    if(closelist.find(x)==closelist.end()) {
                        openlist.push(state(f,g,h,x));
                        }
                    swap(st[i],st[i-3]);
                    }
                }
        }
    if(!flag) cout << "No Solution!" << endl;
    else cout<<step<<endl;

}

int main() {
    vector <int> des;
    for(int i=0;i<8;i++) des.push_back(i+1);
    des.push_back(0);
    destination=cantor(des);
    int T;
    cin >> T;
    while(T--)
        {
            int a;
            stnum.clear();
            for(int i=0;i<9;i++) {cin>>a; stnum.push_back(a);}
            solve();
        }
    return 0;
}

通常A* 还有一个更新

对于第n个F

即 if(F>g(n)+1+H(n-1))  F=g(n)+1+H(n-1)






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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值