wikioi-天梯-提高一等-启发式搜索-1225:八数码难题

Yours和zero在研究A*启发式算法.拿到一道经典的A*问题,但是他们不会做,请你帮他们.
问题描述

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

输入初试状态,一行九个数字,空格用0表示

只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)

283104765

4

类型:图论  难度:3

题意:给出一个3*3的图的初始状态,填有0-8共9个数字,0表示空格,空格周围(上下左右)的数字可以移动到空格中,给定一个目标状态,求从初始状态到目标状态的最小步数。

分析:基本思想是bfs,每次遍历当前状态的所有新的状态,加入一个队列中,然后下一次遍历时队头出队,重复进行直到找到目标状态。

注意:

用一个map记录从初始状态到达每个状态的最小步数,map初始值为0,当有新状态时,若这个状态之前没出现过(map值为0)或者之前记录的步数小于当前步数(用bfs不会出现这个情况),则将新状态入队。(也可以用康托展开计算一个hash值来代替map,属于本题的经典解法)

以上是一个盲目搜索的bfs,这道题数据不强,160ms过。

 

下面讲一下A*算法的方法。

首先定义一个估价函数hs,表示当前状态和目标状态的之间的距离,本题中用当前状态的每个数字到到正确位置的距离的和作为估价函数的值,记做pg。

记录每个状态已经走过的步数为step,那么step+pg就是这个状态到目标状态的总代价。

然后,讲上述的队列换成一个最小堆,按照step+pg的值来比较大小,每次取队头的操作变成每次取最小堆堆顶的元素,即step+pg最小的状态,每次入队即加入最小堆。

这样,搜索过程就从盲目搜索变成了每次取最小估计步数状态的搜索,从搜索过程来讲,已经不是严格意义上的bfs了,因为一些估价较大的状态可能永远不会出队,而算法会沿着估价小的搜索方向,更快的找到目标状态,搜索空间大大降低。使用这个方法,时间为4ms,快了40倍。

注意:我用priority_queue<node>维护一个最小堆,node中存储状态图,step和pg值,注意priority_queue默认是最大堆,所以在node中重载运算符<,>,=,并且反向了意义。

也可以用set来维护。

代码:

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<string>
#include<queue>
#include<cmath>
#include<map>
using namespace std;

string des = "123804765";
int despos[9] = {4,0,1,2,5,8,7,6,3};
int dir[4] = {-3,3,-1,1};

struct node
{
    string s;
    int pg,step;
    bool operator < (node b) const {
        return pg + step > b.pg + b.step ;
    }
    bool operator > (node b) const {
    	return pg + step < b.pg + b.step ;
	}
	bool operator == (node b) const {
    	return pg + step == b.pg + b.step ;
	}
};
priority_queue<node> next;
map<string,int> ans;

int caldis(string now)
{
    int ans = 0;
    for(int i=0; i<now.size(); i++)
    {
        int desp = despos[now[i]-'0'];
        ans += abs(desp/3-i/3)+abs(desp%3-i%3);
    }
    return ans;
}

int dfs(string st)
{
    node start;
    start.s = st;
    start.pg = caldis(st);
    start.step = 0;
    next.push(start);
    
    ans[st] = 1;
    while(!next.empty())
    {
        node now = next.top();
        next.pop();
        //cout<<now<<endl;
        if(now.s == des)
            return ans[now.s]-1;
        
        int zeropos = now.s.find('0',0);
        
        for(int i=0; i<4; i++)
        {
            if(i==0 && zeropos<3)
                continue;
            if(i==1 && zeropos>5)
                continue;
            if(i==2 && zeropos%3==0)
                continue;
            if(i==3 && zeropos%3==2)
                continue;
            node nt;
            nt.s = now.s;
            nt.s[zeropos+dir[i]] = '0';
            nt.s[zeropos] = now.s[zeropos+dir[i]];
            if(ans[nt.s]==0 || ans[nt.s]-1 > now.step + 1)
            {
                ans[nt.s] = ans[now.s]+1;
                nt.pg = caldis(nt.s);
                nt.step = now.step + 1;
                next.push(nt);
            }
        }
    }
    return -1;
}

int main()
{
    string st;
    cin>>st;
    cout<<dfs(st)<<endl;
}


 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值