Yours和zero在研究A*启发式算法.拿到一道经典的A*问题,但是他们不会做,请你帮他们.
问题描述
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
输入初试状态,一行九个数字,空格用0表示
只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)
283104765
4
从昨天上午捣鼓,到今天一直跳了半个上午才弄好。然后我发现,我的代码能力真的非常低下,不编译感觉什么事儿都没有,一调试N个错误,还都是细节上的粗心,逻辑上的失误,思路转换的时候忘了回溯什么的,看来我真得好好磨练代码能力了。每个题目都要自己敲,一不小心可能就有新的发现。
这道题思路还是比较简单的,之前一直纠结于怎样存状态,其实就用朴素的二维数组就可以,3*3不大,也不用传递数组形参或者实参,直接swap,回溯的时候再swap回来就可以了。
具体方法是:每次向上下左右四个方向交换空位和数字,判重,计算估价函数,进入优先队列,更新空位坐标和已走步数,直到找到解。
下面是一些细节上的说明:
1.判重 这个刚开始准备用哈希表,从没写过哈希表,但后来听了里奥神犇的建议用了set 并不慢 就这道题而言,每个数字都是有0-9这九个数字组成,一共只有三十万种状态,况且如果8个数字确定了最后一位也就确定了,用哈希是很好的,不容易出现重叠冲突。但是用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君的故事好励志,所以还是要努力努力···文史哲个人修养也要提高,技术能力也要提高,家务能力也要提高,我要十项全能···什么鬼反正某人说过懂编程懂工程又懂歌剧的人是很有魅力的···
然后膜拜一下维特根斯坦···维特根斯坦这样的名字可能真的要让人如雷贯耳不胜惶恐,恩我要努力成为这样的文青,然而现在我却是这样想的:维特根斯坦小时候和希特勒在一起好萌啊【PIA飞 好吧这说明窝离文青还有很遥远的距离又萌又腐咩【什么鬼 这些书总是要读的 既然提到维特根斯坦就顺便拜一拜罗素这样的神奇人物吧 这些大家族果然修养各种不凡啊···
然后再膜拜一下香浓,啊不对,香农。Shannon这个让我最先想到的是爱尔兰Limerick的那条河,多美的名字,好像去那儿···= =扯回来,既然隔壁小乔他们承认,那看来继图灵和冯诺依曼之后,香农也是相当伟大的人物了。有时候觉得诺依曼费曼这样的天才全才又是性情中人永远只能被我们膜拜的,然而我们更多人终究不是天才,所以我还是开开心心地过我的生活吧。即使我们不是知识的开创者,我们也可以做知识的追随者。这并没有错。而且,我们或许没有如此耀眼的才华,但我们为何不能做一个这样的性情中人呢。性情不是他们的专利,我们只要敢想敢做也是可以的。又想到某剧里评价大学的一句话,如此充满理想主义与纵欲轻狂的一个地方。我觉得,既然年轻就应该怀抱着理想主义,这样的人生才能多彩。我读维特根斯坦,深深地体会到这样一点:只要敢想敢做就可以。我们年轻,更不能在思想上受到束缚,我们应该敢去想,无论对错。茨威格十几岁的时候,生活在欧洲的心脏,在哪个二十世纪的黎明年代,维也纳的年轻人正是在这样一种文化价值全方位兴盛的环境熏陶下变得格外早熟而富有思想力,年轻的评论家们从不吝惜用属于自己的思想大胆解读文化,解读世界,维也纳和柏林那群十五六岁的少年的见地已经令整个欧洲震动。我们或许没有那传承千年的文化环境,没有图书馆和音乐会,但我们也可以努力去做。文化的庸俗是因为人们甚至不愿去想,不愿去思考,只是像虫豸一样碌碌于匆忙的地铁线上,年轻人为考试而重复在竞争中功利地生活,底层人们为生存而挣扎为物质而拼命,上层人们满足于安逸舒适的生活,奉行中庸之道和犬儒主义,把这些当做现实主义的生存之道。很少有人再天真地做着自己所想的事情。理想主义或许在复杂的现实中早已磨灭,但我不希望在年轻人身上看不到这些理想主义的光辉,至少是对自身价值的承认和追求。我们应该思考,我们应该学习,我们应该进步,我们应该快乐。
好像不知不觉弄出来一篇奇怪的作文···还是抓紧回到正题把···
那么上代码吧
//codevs1225 °ËÊýÂëÄÑÌâ BFS+A*+Hash
//ÊÔÒ»ÊÔ
//copyright by ametake
#include
#include
#include
#include
#include
#include
using namespace std; const int t[3][3]= {{1,2,3}, {8,0,4}, {7,6,5}}; const int xx[4]={1,0,0,-1}; const int yy[4]={0,1,-1,0}; struct node { int a[3][3]; int x,y,id,eva;//the position of 0,steps have walked,ans evaluated value; bool operator < (node b) const { return eva>b.eva; } }e,f,g; bool ok=false; priority_queue
q; set
hash; 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 } int geteva(node &now) { int va=0; for (int i=0;i<3;i++) { for (int j=0;j<3;j++) { if (t[i][j]!=now.a[i][j]) va++; } } return va+now.id; } 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--; } } } int main() { char s[12]; scanf("%s",s); int he=0; 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]; } } e.id=0; hash.insert(he);//use count to check q.push(e); bfs(); if (!ok) printf("= ="); return 0; }
——卧龙跃马终黄土,人事音书漫寂寥