首页 > 代码库 > 【基础练习】【BFS+A*】codevs1225八数码难题题解

【基础练习】【BFS+A*】codevs1225八数码难题题解

题目描写叙述 Description

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

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

输入描写叙述 Input Description

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

输出描写叙述 Output Description

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

例子输入 Sample Input

283104765

例子输出 Sample Output

4

血泪史,血泪史啊TUT

从昨天上午捣鼓,到今天一直跳了半个上午才弄好。然后我发现,我的代码能力真的很低下。不编译感觉什么事儿都没有,一调试N个错误,还都是细节上的粗心。逻辑上的失误。思路转换的时候忘了回溯什么的。看来我真得好好磨练代码能力了。

每一个题目都要自己敲,一不小心可能就有新的发现。


这道题思路还是比較简单的。之前一直纠结于如何存状态。事实上就用朴素的二维数组就能够。3*3不大。也不用传递数组形參或者实參,直接swap,回溯的时候再swap回来就能够了。


详细方法是:每次向上下左右四个方向交换空位和数字。判重,计算估价函数,进入优先队列,更新空位坐标和已走步数,直到找到解。

以下是一些细节上的说明:

1.判重 这个刚開始准备用哈希表,从没写过哈希表,但后来听了里奥神犇的建议用了set 并不慢 就这道题而言。每一个数字都是有0-9这九个数字组成,一共仅仅有三十万种状态,况且假设8个数字确定了最后一位也就确定了,用哈希是非常好的,不easy出现重叠冲突。

可是用set更加简明易行,直接计算数字insert进去就能够了,查找时能够用count函数检查。

2.估价函数eva:eva是evaluate 估价(动词) 或者evaluation 估价(名词)的缩写。

这次顺便学习了数学上和国际象棋上的三个距离:

曼哈顿距离:两点横坐标差点绝对值和纵坐标差的绝对值之和。

名字来源于曼哈顿的街道都是矩形网格状的,从某点到还有一点的距离就是曼哈顿距离。

欧拉距离:两点间的直线距离。计算公式是两点左边差绝对值的平方和。相当于已知三角形直角边求三角形最长边 

切比雪夫距离:两点横纵坐标差绝对值中较大的一个。

曼哈顿距离是求和。这里是取max。(让我想起某个诸城一中讲的离散化的题目) 


原本最准确应当採用曼哈顿距离。由于格子仅仅能上下左右移动。可是曼哈顿距离的计算我仅仅会最朴素的四次方求解,不知有没有简单方法。其实,在这里我们也能够用像骑士精神那道题里出现的推断与目标状态不同的各自有多少,这个是平方的复杂度,可是经模拟,效果是相同的。它相同满足估价函数值均不大于实际值。

因为估价函数满足假设估价函数值均不大于实际值,那么向估价函数小的方向进展一定能达到最优解。这一条我不知道怎样证明但已被证明。因此我们能够採用统计有多少个不同格子的估价做法。

3.一些细节。关于这道题目中我犯的N个错误

A.注意常量数组所实用的是大括号!。!(pas是小括号)

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

B.优先队列的重载。因为优先队列默认大的在前,假设想要让eva小的在前,应该把大于号重载成小于号。因为涉及STL(优先队列)。应当在后面加上const

bool operator < (node b) const
    {
        return eva>b.eva;
    }

C.关于set版哈希的处理,这里是把每一个状态转化为一个int数字放入集合中

bool gethash(node &now)
{
    int he=0;
    for (int i=0;i<3;i++)
    {
        for (int j=0;j<3;j++)
        {
            he=he*10+now.a[i][j];
        }
    }
    if (hash.count(he)) return false;
    hash.insert(he);
    return true;
    //Èç¹ûûÓÐÕâ¸öÊý¾Í¹þÏ£ÁË·ñÔò·µ»Øfalse 
}
再看以下原始状态的数字转化

for (int i=0;i<3;i++)
    {
        for (int j=0;j<3;j++)
        {
            e.a[i][j]=s[i*3+j]-'0';
            if (e.a[i][j]==0)
            {
                e.x=i;
                e.y=j;
            }
            he=he*10+e.a[i][j];
        }
    }

注意,以下是吧读入的字符转换为数字,而上面的数组中存储的本来就是数字,直接计算就能够了。然而我上面那个写成了now.a[i][j]-‘0‘ 于是全是负数···于是set不会报反复···于是死循环···

D.bfs的细节问题(单步了N次TUT)

void bfs()
{
    while (!q.empty())
    {
        if (ok) return;
        node now=q.top();
        q.pop();
        int nx,ny;
        for (int i=0;i<4;i++)
        {
            nx=now.x+xx[i];
            ny=now.y+yy[i];
            if (nx<0||nx>2||ny<0||ny>2) continue;
            swap(now.a[nx][ny],now.a[now.x][now.y]);
            if (!gethash(now))
			{
				swap(now.a[nx][ny],now.a[now.x][now.y]);
				continue;
			} //Çó¹þÏ£ ×¢ÒâÈç¹û²»³ÉÁ¢Ó¦·µ»ØÔ­À´µÄÖµ£¡£¡£¡ 
            now.x=nx;
            now.y=ny;
            now.id++;
            now.eva=geteva(now);//Çó¹À¼Ûº¯Êý 
            if (now.eva==now.id)
            {
                printf("%d\n",now.id);//×îÓÅÐÔ£¨²½Êý×îÉÙ£©³ÉÁ¢Ô­Àí£º¹À¼Ûº¯ÊýÖµ²»´óÓÚʵ¼ÊÖµ£¬Ôò½â±Ø¶¨×îÓÅ 
                ok=true;
                break;
            }
            q.push(now);
            now.x-=xx[i];
            now.y-=yy[i];
            swap(now.a[nx][ny],now.a[now.x][now.y]);
            now.id--;
        }
    }
}

这是BFS部分的代码。代码应该非常好懂。

可是···

第一次。发现我改变了now之后没有回溯,于是非常多状态一时半会儿找不到。TLE···

第二次,发现空格的坐标在每一个状态中并没有更新,于是空格根本无法到达角格,输出无解(“= =”)···

第三次,发现我并没有把now这个节点的状态全然恢复到原来。id并没有--。导致步数紊乱仅仅增不减,WA···

第四次。发现假设出现了已经有过的状态。虽然判重没有问题仍然进入队列且其它状态被忽略···细致一看原来gethash返回false后直接continue并没有回溯。这样now在下一次循环的状态就延续了这个已经出现过的状态···死循环···

第五次,发现eva函数的返回值不大对,事实上应该返回已走步数+估价函数。可是我没有加已走步数,这道题小数据不受影响,可是后果慘重···

第六次,发现空格坐标更新后没有回溯···于是又调试···

凡此种种,吐血一上午,几近崩溃,于是和大家争论tarjan究竟应该读什么,后来听了音频。有道上念作tar zhen 恩 能够接受 然而我们居然一直没有看出前面的那个expression居然是深度广度优先搜索···

总之,吸取教训。以后要好好提高代码能力了

昨天上知乎又受到了教育·1··还是应该努力学习,好好读书。争取成为一名优秀的技宅和出色的学霸···rio君的故事好励志,所以还是要努力努力···文史哲个人修养也要提高。技术能力也要提高,家务能力也要提高,我要十项全能···什么鬼反正某人说过懂编程懂project又懂歌剧的人是非常有魅力的···

然后膜拜一下维特根斯坦···维特根斯坦这种名字可能真的要让人如雷贯耳不胜惶恐。恩我要努力成为这种文青。然而如今我却是这样想的:维特根斯坦小时候和希特勒在一起好萌啊【PIA飞 好吧这说明窝离文青还有非常遥远的距离又萌又腐咩【什么鬼 这些书总是要读的 既然提到维特根斯坦就顺便拜一拜罗素这种奇妙人物吧 这些大家族果然修养各种不凡啊···

然后再膜拜一下香浓,啊不正确。香农。

Shannon这个让我最先想到的是爱尔兰Limerick的那条河,多美的名字,好像去那儿···= =扯回来,既然隔壁小乔他们承认,那看来继图灵和冯诺依曼之后,香农也是相当伟大的人物了。有时候认为诺依曼费曼这种天才全才又是性情中人永远仅仅能被我们膜拜的,然而我们很多其它人终究不是天才,所以我还是开开心心地过我的生活吧。即使我们不是知识的开创者,我们也能够做知识的追随者。这并没有错。并且。我们也许没有如此耀眼的才华,但我们为何不能做一个这种性情中人呢。性情不是他们的专利。我们仅仅要敢想敢做也是能够的。又想到某剧里评价大学的一句话。如此充满理想主义与纵欲轻狂的一个地方。我认为,既然年轻就应该怀抱着理想主义,这种人生才干多彩。我读维特根斯坦,深深地体会到这样一点:仅仅要敢想敢做就能够。我们年轻。更不能在思想上受到束缚,我们应该敢去想,不管对错。茨威格十几岁的时候,生活在欧洲的心脏,在哪个二十世纪的黎明年代,维也纳的年轻人正是在这样一种文化价值全方位兴旺的环境熏陶下变得格外早熟而富有思想力。年轻的评论家们从不吝惜用属于自己的思想大胆解读文化,解读世界。维也纳和柏林那群十五六岁的少年的见地已经令整个欧洲震动。我们也许没有那传承千年的文化环境。没有图书馆和音乐会,但我们也能够努力去做。

文化的庸俗是由于人们甚至不愿去想。不愿去思考,仅仅是像虫豸一样碌碌于匆忙的地铁线上,年轻人为考试而反复在竞争中功利地生活,底层人们为生存而挣扎为物质而拼命,上层人们满足于安逸舒适的生活。奉行中庸之道和犬儒主义。把这些当做现实主义的生存之道。非常少有人再天真地做着自己所想的事情。理想主义也许在复杂的现实中早已磨灭,但我不希望在年轻人身上看不到这些理想主义的光辉。至少是对自身价值的承认和追求。我们应该思考,我们应该学习,我们应该进步,我们应该快乐。

好像不知不觉弄出来一篇奇怪的作文···还是抓紧回到正题把···

那么上代码吧 

<script src="https://code.csdn.net/snippets/968106.js" type="text/javascript"></script>

——卧龙跃马终黄土,人事音书漫寂寥

【基础练习】【BFS+A*】codevs1225八数码难题题解