HIHO #1312 : 搜索三·启发式搜索

32 篇文章 1 订阅
7 篇文章 0 订阅

题目链接

A*搜索入门,这个文章讲解的很清晰了,Here
hiho里面讲解的也很清楚
经典的八数码问题,在搜索的时候把棋盘当前的状态,看做一个1-9的排列(我把0换成了9),但是要对于每一个排列进行排重, 有一个比较好的方法是 康拓展开,及其逆展开,可以计算一个排列在形成的所有排列中的名次,以及给出一个名次,还原出这个排列。剩下的就是A*搜索
主要是bfs,然后加上剪枝,即F函数
G:表示出起点到当前点实际的距离
H:当前点到目标点的估计值(不大于实际值才可以)
F:H+G,确定每个点的搜索的优先级

A*真的很快,跑了200+ms
直接bfs的跑了5000+ms

A*的

#include<bits/stdc++.h>
using namespace std;
#define cl(a,b) memset(a,b,sizeof(a))
#define fastIO ios::sync_with_stdio(false);cin.tie(0);
#define LL long long
#define pb push_back
#define gcd __gcd


#define For(i,j,k) for(int i=(j);i<=k;i++)
#define lowbit(i) (i&(-i))
#define _(x) printf("%d\n",x)

typedef vector<LL> vec;
typedef pair<int,int> PI;
const double EPS = 1e-8;
const int maxn = 5e6;
const int inf  = 1 << 28;

int fac[10];
void init(){
    fac[0] = fac[1] = 1;
    for(int i=2;i<=9;i++)fac[i] = fac[i-1] * i;
}
int n = 9;
int arrayToInt(int a[]){       //编码一个排列
    int ans=0,i,j,tmp;
    for(i=1;i<=n;i++){
        tmp = 0;
        for(j=i+1;j<=n;j++)if(a[j]<a[i])tmp++;
        ans+=fac[n-i]*tmp;
    }
    return ans;
}

int fun(int a[3][3]){       //二维数组展开一维,再调用康拓展开
    int b[10],k=1;
    for(int i=0;i<3;i++)for(int j=0;j<3;j++){
        b[k++] = a[i][j];
    }
    return arrayToInt(b);
}

int getH(int a[3][3]){      //计算估价函数,这里用当前状态到目标状态的曼哈顿距离
    int ans=0;
    for(int i=0;i<3;i++){
        for(int j=0;j<3;j++){
            ans += abs(i - (a[i][j] - 1) / 3) + abs(j - (a[i][j] - 1) % 3);
        }
    }
    return ans;
}

int dir[4][2]={0,-1,0,1,-1,0,1,0};
struct node{
    int a[3][3];
    int x,y;        //9的位置
    int F;          //启发函数F (F = G + H);
    int G,H;        //G:起点到当前点的步数,H:目标点到当前点期望的步数

    bool operator<(const node&rhs) const{
        return F > rhs.F || F == rhs.F && G > rhs.G;
    }
}st;
bool vis[maxn];

int bfs(){

    st.G = 0;
    st.H = getH(st.a);
    st.F = st.G + st.H;

    priority_queue<node> q;//相当于文中openlist,vis数组相当于closedlist
    cl(vis,false);

    q.push(st);
    if(fun(st.a)==0)return 0;

    while(!q.empty()){
        node tmp = q.top();q.pop();

        for(int i=0;i<4;i++){
            node cur = tmp;
            cur.x += dir[i][0];
            cur.y += dir[i][1];
            if(cur.x<0||cur.y<0||cur.x>2||cur.y>2)continue;
            swap(cur.a[cur.x][cur.y], cur.a[tmp.x][tmp.y]);

            int val = fun(cur.a);
            if(!vis[val]){
            /*表示不在closedlist中,要么在openlist中,
            要么不在openlist,也就是第一次访问,都进行更新。
            如果已经在openlist中,为什么还要更新?
            因为某一个点的G可能会在后来被更新为一个较小的值,
            这样他的F也会变小,所以还是要加入,这里我没有直接找到那个点进行修改,
            而是直接在加入一个这样的新点,由排序的条件,使得他排在前面*/
                cur.G = tmp.G + 1;
                cur.H = getH(cur.a);
                cur.F = cur.G + cur.H;
                q.push(cur);
            }
            if(val == 0) return cur.G;
        }
        vis[fun(tmp.a)] = true;//遍历完当前点可达的所有点,那么当前点就可以加入closedlist表
    }
    return -1;
}

int a[3][3];
int main(){
    init();
    int T;scanf("%d",&T);
    while(T--){

        for(int i=0;i<3;i++){
            for(int j=0;j<3;j++){
                scanf("%d",&st.a[i][j]);
                if(st.a[i][j]==0){
                    st.a[i][j] = 9;st.x = i;st.y = j;
                }
            }
        }
        int ans = bfs();
        if(ans == -1)
            puts("No Solution!");
        else
            printf("%d\n",ans);
    }
    return 0;
}

/*
10
5 8 0
2 7 4
3 1 6
3 4 2
5 7 0
6 8 1

26 25

*/

直接bfs的

#include<bits/stdc++.h>
using namespace std;
#define cl(a,b) memset(a,b,sizeof(a))
#define fastIO ios::sync_with_stdio(false);cin.tie(0);
#define LL long long
#define pb push_back
#define gcd __gcd


#define For(i,j,k) for(int i=(j);i<=k;i++)
#define lowbit(i) (i&(-i))
#define _(x) printf("%d\n",x)

typedef vector<LL> vec;
typedef pair<int,int> PI;
const double EPS = 1e-8;
const int maxn = 4e6;
const int inf  = 1 << 28;

int fac[10];
void init(){
    fac[0] = fac[1] = 1;
    for(int i=2;i<=9;i++)fac[i] = fac[i-1] * i;//cout<<fac[i]<<endl;
}

/*
O(n*n)
全排列散列,康拓展开,康拓逆展开
n数据范围,a[] 排列数组,下标1开始
ans:排名,0开始
*/
int n = 9;
void intToArray(int x,int a[]){
    bool used[10];cl(used,false);
    int i,j,tmp;
    for(i=1;i<=n;i++){
        tmp = x / fac[n-i];
        for(j=1;j<=n;j++)if(!used[j]){
            if(tmp==0)break;tmp--;
        }
        a[i] = j;
        used[j] = true;
        x %= fac[n-i];
    }
}
int arrayToInt(int a[]){
    int ans=0,i,j,tmp;
    for(i=1;i<=n;i++){
        tmp = a[i] - 1;
        for(j=1;j<i;j++)if(a[j]<a[i])--tmp;
        ans+=fac[n-i]*tmp;
    }
    return ans;
}
//--end


int fun(int a[3][3]){
    int b[10],k=1;
    for(int i=0;i<3;i++)for(int j=0;j<3;j++){
        b[k++] = a[i][j];
    }
    return arrayToInt(b);
}


int step[maxn];
int dir[][4]={{0,0,1,-1},{-1,1,0,0}};
int bfs(int st){

    queue<int> q;
    cl(step,-1);

    q.push(st);
    step[st] = 0;

    while(!q.empty()){
        //
        int cur = q.front();q.pop();
        if(cur == 0) return step[cur];
        int tmp[10], tmp2[10];
        intToArray(cur,tmp);
        int Rank ;
        for(int i=1;i<=9;i++)if(tmp[i]==9){Rank = i - 1;break;}
        int x = Rank / 3;
        int y = Rank % 3;

        for(int i=0;i<4;i++){
            int dx = x + dir[0][i];
            int dy = y + dir[1][i];
            if(dx < 0 || dy < 0 || dx >= 3 || dy >= 3)continue;
            //printf("dx = %d, dy = %d\n",dx,dy);
            int t = dx * 3 + dy + 1;
            memcpy(tmp2,tmp,sizeof(tmp));
            //printf("==================%d %d\n",tmp2[Rank+1],tmp2[t]);
            swap(tmp2[Rank+1],tmp2[t]);
            int X = arrayToInt(tmp2);
            if(step[X]!=-1)continue;
            step[X] = step[cur] + 1;
            q.push(X);
        }
    }
    return -1;
}

int a[3][3];
int main(){
    int T;init();
    scanf("%d",&T);
    while(T--){
        for(int i=0;i<3;i++){
            for(int j=0;j<3;j++){
                scanf("%d",&a[i][j]);
                if(a[i][j]==0)a[i][j] = 9;
            }
        }

        int ans = bfs(fun(a));
        if(ans == -1){
            puts("No Solution!");
        }
        else printf("%d\n",ans);
    }
    return 0;
}

如果写错了,欢迎打骂,批评指正

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值