评价函数
f(n)=g(n)+h(n)
n代表被评价的结点;
g(n)代表从初始结点s到结点n的最短路径的耗散值预测;
h(n)代表从结点n到目标结点g的最短路径的耗散值预测;
f(n)代表从初始节点s经过结点n到目标结点g的最短路径的耗散值预测。
当在算法A中的评价函数中使用的启发式函数h(n)是处在h*(n)的下界范围(h(n)<=h*(n))时,算法成为A*算法。
伪代码
while(OPEN!=NULL)
{
从OPEN表中取f(n)最小的节点n;
if(n节点==目标节点)
break;
for(当前节点n的每个子节点X)
{
计算f(X);
if(XinOPEN)
if(新的f(X)<OPEN中的f(X))
{
把n设置为X的父亲;
更新OPEN表中的f(n);
}
if(XinCLOSE)
continue;
if(Xnotinboth)
{
把n设置为X的父亲;
求f(X);
并将X插入OPEN表中;//还没有排序
}
}//endfor
将n节点插入CLOSE表中;
按照f(n)将OPEN表中的节点排序;//实际上是比较OPEN表内节点f的大小,从最小路径的节点向下进行。
}//endwhile(OPEN!=NULL)
八数码问题
八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。
初始状态->目标状态:
代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;
struct knight{
int maze[3][3]; //八数码具体情况
int x,y;
int Hash;
int g,h,f; // 估价函数名称
bool operator < (const knight k) const{ //重载比较运算符
return f > k.f;
}
}k;
int tmp1[3][3]; //初始状态和目标状态矩阵
int visited[400000]; //已访问标记(CLOSED列表)
int previous[400000]; //路径保存
int HASH[9]={1,1,2,6,24,120,720,5040,40320}; //HASH的权值
int destination=131976; //目标情况的HASH值
/*
目标状态:
1 2 3
8 4
7 6 5
其hash值为131976
*/
const int dirs[4][2]={{0,1},{0,-1},{1,0},{-1,0}}; //4个移动方向
priority_queue<knight> que; //最小优先级队列(OPEN列表)
void debug1(const knight tmp){
for(int i=0;i<3;i++){
for(int j=0;j<3;j++)
printf("%d ",tmp.maze[i][j]);
printf("\n");
}
printf("%d %d\n%d %d\n",tmp.x,tmp.y,tmp.g,tmp.h);
printf("hash=%d\n",tmp.Hash);
}
void init()
{
printf("初始状态:\n");
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 3; j++)
{
scanf("%d", &tmp1[i][j]);
if(tmp1[i][j] == 0)
{
k.maze[i][j] = 0;
k.x = i;
k.y = j;
}
else
k.maze[i][j] = tmp1[i][j];
}
}
}
bool in(const knight & a){ //判断knight是否在边框内
if(a.x < 0 || a.y < 0 || a.x >= 3 || a.y >= 3)
return false;
return true;
}
int get_h(const knight a){ //估价函数
int ans = 0;
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
if(a.maze[i][j])
ans+=abs(i-(a.maze[i][j]-1)/3)+abs(j-(a.maze[i][j]-1)%3);
return ans;
}
int get_hash(const knight tmp){ //获得HASH值
int a[9],kk=0;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
a[kk++]=tmp.maze[i][j];
int res=0;
for(int i=0;i<9;i++){
int kk=0;
for(int j=0;j<i;j++)
if(a[j]>a[i])
kk++;
res+=HASH[i]*kk;
}
return res;
}
void Astar(){ //A*算法
knight u, v;
while(!que.empty()){
u=que.top();
que.pop();
for(int i = 0; i < 4; i++){
v = u;
v.x += dirs[i][0];
v.y += dirs[i][1];
if(in(v)){
swap(v.maze[v.x][v.y], v.maze[u.x][u.y]); //将空位和相邻位交换
v.Hash = get_hash(v); //得到HASH值
if(visited[v.Hash] == -1){ //判断是否已遍历且是否可行,后者可以不要
visited[v.Hash] = i; //保存方向
v.g++;; //已花代价+1
previous[v.Hash] = u.Hash; //保存路径
v.h = get_h(v); //得到新的估价函数H
v.f = v.h + v.g;
que.push(v); //入队
}
if(v.Hash == destination)
return ;
}
}
}
}
void print(){
int ans[100];
int l = 0, ll = 1;
int nxt = destination;
//printf("%d",previous[nxt]);
while(previous[nxt] != -1){ //从终点往起点找路径
switch(visited[nxt]){ //四个方向对应
case 0:ans[l++]=0;break;
case 1:ans[l++]=1;break;
case 2:ans[l++]=2;break;
case 3:ans[l++]=3;break;
}
nxt = previous[nxt];
}
for(int i = l-1; i >= 0; i--)
{
if(ans[i] == 0)
printf("%d:空格右边的数字挪动\n",ll++);
else if(ans[i] == 1)
printf("%d:空格左边的数字挪动\n",ll++);
else if(ans[i] == 2)
printf("%d:空格下边的数字挪动\n",ll++);
else if(ans[i] == 3)
printf("%d:空格上边的数字挪动\n",ll++);
}
printf("\n");
}
int main()
{
int flag = 0;
while(true)
{
printf("Case %d:\n", flag++);
memset(visited,-1,sizeof(visited));
memset(previous,-1,sizeof(previous));
init();
printf("哈希值:%d\n",get_hash(k));
k.Hash = get_hash(k);
if(k.Hash == destination){ //起始状态为目标状态
printf("初始状态即为目标状态\n");
continue;
}
visited[k.Hash] = -2;
k.g = 0;
k.h = get_h(k);
k.f = k.h + k.g;
while(!que.empty()) que.pop();
que.push(k);
Astar();
print();
}
return 0;
}