总结:利用A*来解决八数码问题,状态很好找,每次移动空格就会形成一种新的状态,
八数码游戏包括一个3X3的棋盘,棋盘上摆放着8个数字的棋子,留下一个空位。与空位相邻的棋子可以滑动到空位中。游戏的目的是要达到一个特定的目标状态。标注的形式化如下(举例):
2 | 3 | |
---|---|---|
1 | 8 | 4 |
7 | 6 | 5 |
(初始状态)
1 | 2 | 3 |
---|---|---|
8 | 4 | |
7 | 6 | 5 |
(目标状态)
优先队列BFS算法,因为A*算法就是带有估价函数的优先队列BFS。
该算法维护了一个优先队列(二叉堆),不断从堆中取出“当前代价最小”的状态(堆顶)去进行扩展。但是它得到只是初态到该状态的最小代价(并没有去考虑从该状态出发到目标状态的情况)
举例说明:
如果给定一个“目标状态”,需要求出从初态到目标状态的最小代价,优先队列BFS显然不行。因为一个状态的当前代价最小,只能说明从起始状态到该状态的代价很小,而在未来的探索中,从该状态到目标状态可能会花费很大的代价;另外一些状态虽然当前代价略大,但是未来到目标状态的代价可能会很小,于是从起始状态到目标状态的总代价反而更优。
于是,我们为了解决上面的问题:可以对未来可能产生的代码进行预估。详细地讲,我们去设计一个**“估价函数”**,以任意状态为输入,计算出从该状态到目标状态所需代价的估计值。在搜索之中,仍然维护了一个堆,不断从堆中取出“当前代价+未来估价”最小的状态来进行扩展。
为了保证第一次从堆中取出目标状态时得到的就是最优解,我们设计的估价函数需要满足一个基本准则:
设当前状态state到目标状态所需代价的估计值为f(state);设在未来的探索中,实际求出的从当前状态state到目标状态的最小代价为g(state)
对于任意的state,应该有f(state)<=g(state)
也就是说,估价函数的估值不能大于未来实际代价,估价比实际代价更优。估价函数f(n)=g(n)+h(n)
这种带有估价函数的优先队列BFS就成为A* 算法。只要保证对于任意状态state,都有f(state)<=g(state),A* 算法就一定能在目标状态第一次从堆中被取出时得到最优解,并且在搜索过程中每个状态只需要扩展一次(之后再被取出就可以直接忽略 )。估价f(state)越准确,越接近g(state),A*算法的效率越高。如果估价始终为0,就等于普通的优先队列BFS。
A算法又称为启发式搜索算法。对启发式搜索算法,又可根据搜索过程中选择扩展节点的范围,将其分为全局择优搜索算法和局部择优搜索算法。
在全局择优搜索中,每当需要扩展节点时,总是从 Open 表的所有节点中选择一个估价函数值最小的节点进行扩展。其搜索过程可能描述如下:
这里采用的启发式策略为:f(n) = g(n) + h(n),其中g(n)为从初始节点到当前节点的步数(层数),h(n)为 当前节点 “不在位 ”的方块数(也就是说不在位的方块数越少,那么临目标状态越近)例如下图中的h(n)=5,有的讲解的是不包含空格,我这里是包含了的,经测试只要前后标准一致,包不包含空格都一样。
g(n)为已经消耗的实际代价,即已经走了的步数。
h(n)为预测路径,即还有几个数字待走。
#include
#include
#include
using namespace std;
char arr[10], brr[10] = "123804765";
struct node {
int num, step, cost, zeroPos;
bool operator<(const node& a)const {
return cost > a.cost;
}
node(int n, int s, int p) {
num = n, step = s, zeroPos = p;
setCost();
}
void setCost() {
char a[10];
int c = 0;
sprintf(a, "%09d", num);
for (int i = 0; i < 9; i++)
if (a[i] != brr[i])
c++;
cost = c + step;
}
};
int des = 123804765;
int changeId[9][4] = {
{
-1,-1,3,1},{
-1,0,4,2},{
-1,1,5,-1},
{
0,-1,6,4},{
1,3,7,5},{
2,4,8,-1},
{
3,-1,-1,7},{
4,6,-1,8},{
5,7,-1,-1} };
map<int, bool>mymap;
priority_queue<node> que;//优先级队列
void swap(char* ch, int a, int b) {
char c = ch[a]; ch[a] = ch[b]; ch[b] = c; }
int bfsHash(int start, int zeroPos) {
char temp[10];
node tempN(start, 0, zeroPos);//创建一个节点
que.push(tempN);//压入优先级队列
mymap[start] = 1;//标记开始节点被访问过
while (!que.empty()) {
tempN = que.top();
que.pop();//弹出一个节点
sprintf(temp, "%09d", tempN.num);
int pos = tempN.zeroPos, k;
for (int i = 0; i < 4; i++) {
if (changeId[pos][i] != -1) {
swap(temp, pos, changeId[pos][i]);
sscanf(temp, "%d", &k);
if (k == des)return tempN.step + 1;
if (mymap.count(k) == 0) {
node tempM(k, tempN.step + 1, changeId[pos][i]);
que.push(tempM);//创建一个新节点并压入队列
mymap[k] = 1;
}
swap(temp, pos, changeId[pos][i]);
}
}
}
}
int main() {
int n, k, b;
scanf("%s", arr);
for (k = 0; k < 9; k++)
if (arr[k] == '0')break;
sscanf(arr, "%d", &n);
b = bfsHash(n, k);
printf("%d步即可变换完成", b);
return 0;
}
changeId[9][4] = {
{
-1,-1,3,1},{
-1,0,4,2},{
-1,1,5,-1},
{
0,-1,6,4},{
1,3,7,5},{
2,4,8,-1},
{
3,-1,-1,7},{
4,6,-1,8},{
5,7,-1,-1} };
这个数组也就代表九个位置中四个方向的可置换的数组元素下标(-1表示该方向不能交换),注意:这里的四个方向的话,是逆时针,(上左下右)
#include
#include
using namespace std;
char arr[10],brr[10]="123804765";
struct node{
int num,step,cost,zeroPos;
bool operator<(const node &a)const{
return cost>a.cost;
}
node(int n,int s,int p){
num=n,step=s,zeroPos=p;
setCost();
}
void setCost(){
char a[10];
int c=0;
sprintf(a,"%09d",num);
for(int i=0;i<9;i++)
if(a[i]!=brr[i])
c++;
cost=c+step;
}
};
int changeId[9][4]={
{
-1,-1,3,1},{
-1,0,4,2},{
-1,1,5,-1},
{
0,-1,6,4},{
1,3,7,5},{
2,4,8,-1},
{
3,-1,-1,7},{
4,6,-1,8},{
5,7,-1,-1}};
const int M=2E+6,N=1000003;//362897;
int hashTable[M];//hashtable中key为hash值,value为被hash的值
int Next[M],des=123804765;//next表示如果在某个位置冲突,则冲突位置存到hashtable[next[i]]
priority_queue<node> que;//优先级队列
int Hash(int n){
return n%N;
}
bool tryInsert(int n){
int hashValue=Hash(n);
while(Next[hashValue]){
//如果被hash出来的值得next不为0则向下查找
if(hashTable[hashValue]==n)//如果发现已经在hashtable中则返回false
return false;
hashValue=Next[hashValue];
}//循环结束hashValue指向最后一个hash值相同的节点
if(hashTable[hashValue]==n)//再判断一遍
return false;
int j=N-1;//在N后面找空余空间,避免占用其他hash值得空间造成冲突
while(hashTable[++j]);//向后找一个没用到的空间
Next[hashValue]=j;
hashTable[j]=n;
return true;
}
void swap(char* ch,int a,int b){
char c=ch[a];ch[a]=ch[b];ch[b]=c;}
int bfsHash(int start,int zeroPos){
char temp[10];
node tempN(start,0,zeroPos);
que.push(tempN);
while(!que.empty()){
tempN=que.top();
que.pop();
sprintf(temp,"%09d",tempN.num);
int pos=tempN.zeroPos,k;
for(int i=0;i<4;i++){
if(changeId[pos][i]!=-1){
swap(temp,pos,changeId[pos][i]);
sscanf(temp,"%d",&k);
if(k==des)return tempN.step+1;
if(tryInsert(k)){
//插入新状态成功,则说明新状态没有被访问过
node tempM(k,tempN.step+1,changeId[pos][i]);
que.push(tempM);
}
swap(temp,pos,changeId[pos][i]);
}
}
}
}
int main(){
int n,k,b=0;
scanf("%s",arr);
for(k=0;k<9;k++)
if(arr[k]=='0')break;
sscanf(arr,"%d",&n);
if(n!=des)
b=bfsHash(n,k);
printf("%d步即可变换完成",b);
return 0;
}