前言:在网上看了很多有关A*算法的资料,不少文章讲得很详细,分析得很透彻,但仅仅阅读网上的资料,我总觉得这样的学习过程过于肤浅,于是自己实现了一个A*算法比较经典的问题,即文章标题所讲的八数码问题。
问题描述:八数码问题,实际就是在一个3X3的九宫格内,其中一个格子为空,其余八个格子分别用1-8的数字填充,这八个数字在九宫格内所占格子的位置可以任意。我们所求就是在两种占位置的情况下,如何从其中一种情况,转移到另一种情况?当然,限制条件是:移动过程中,只能是空格周围的格子向空格移动。(有点儿类似我们小时候玩的一种移动滑块拼图的游戏。)
问题分析:1、首先是否一个所有状态之间都有互通性?即是否不论那八个填充数字的格子在九宫格内呈现什么样的位置摆放,都能在两种状态之间转换。
答案是否定的。且先不管空格,就这八个数字之间我们从左往右,从上到下排列总会出现一个序列,数字的序列我们可以通过计算逆序数排出一个奇排列和偶排列,显然九宫格的状态只能在奇排列与奇排列之间、偶排列与偶排列之间转换。原因是:我们每一次异动空格改变序列的逆序数都是改变偶数个序数,故不管怎么移动空格,奇排列与偶排列之间都无法转换。
2、状态之间如何转换?
我们用一个图来说明:
3、既然每一个状态都会衍生出这么多状态,我们如何选择一条合适的状态迁移路径?
大致有三种策略,宽度优先(BFS),深度优先(DFS),还有就是本文所采用的启发式搜索方式:A*算法。前两种方式都是盲目性搜索,在A*算法中,一个结点位置的好坏用估价函数来对它进行评估,通过评估来选择下一个访问状态。
A*算法的估价函数可表示为: f'(n) = g'(n) + h'(n) 。这里,f'(n)是估价函数,g'(n)是起点到终点的最短路径值(也称为最小耗费或最小代价),h'(n)是n到目标的最短路经的启发值。
由于这个f'(n)其实是无法预先知道的,所以实际上使用的是下面的估价函数:f(n) = g(n) + h(n)
其 中g(n)是从初始结点到节点n的实际代价,h(n)是从结点n到目标结点的最佳路径的估计代价。在这里主要是h(n)体现了搜索的启发信息,因为 g(n)是已知的。用f(n)作为f'(n)的近似,也就是用g(n)代替g'(n),h(n)代替h'(n)。这样必须满足两个条件:(1)g(n)>=g'(n)(大多数情况下都是满足的,可以不用考虑),且f必须保持单调递增。(2)h必须小于等于实际的从当前节点到达目标节点的最小耗费h(n)<=h'(n)。第二点特别的重要。可以证明应用这样的估价函数是可以找到最短路径的。
4、如何实现A*算法呢?
A*算法的步骤如下:
1)建立两个链表,一个open链表,用于存放待访问的结点,一个close链表,用于存放已经访问的结点。计算初始结点的估价函数f,并将初始结点方式open表。
2)取出open表的结点,如果该结点是目标结点,则输出路径,程序结束。否则对结点进行扩展。
3)首先检查扩展出的是否存在于close表中,若存在,直接抛弃该节点;若不存在,再检查open表,若在open表中,则比较两个结点的fValue值,用小的代替大的,若不存在,直接插入open表。
4)如果该结点还可以扩展,直接返回第3)步。否则返回第2)步。
根据以上分析,下面是我用C语言实现的代码。(注:网上大多用C++实现,代码简洁,总共也就两百行左右。我自己由于对C++不熟悉,用C实现了一下,代码比较累赘,主要是链表的处理比较麻烦。)
文件:"h1.h"
#ifndef H1_H
#define H1_H
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
#include<process.h>
#include<math.h>
#define MAXSIZE 100
struct StateNode{ //State struct of each step.
int grid[3][3];
int fValue;
int gValue;
struct StateNode *parent;
};
struct LinkNode{ //The list maintaining the states visited and to visit.
StateNode *stateNode;
struct LinkNode *next;
};
void findSpace(StateNode currentState, int &x, int &y);//Find the position of '0'.
int calculateManhattanDistance(StateNode currentState, StateNode aimState, int value);//Calculate the Manhattan Distance of two states.
int fValueCalculation(StateNode currentState, StateNode aimState); //Calculate the f value of current state
int isStatesEqual(StateNode state1, StateNode state2); //Decide whether the two states equal, equal return 1, otherwise, return 0.
int stateSpreadAble(StateNode currentState, int direction );
int stateTransformFeasible(StateNode startState, StateNode aimState);
void insertStateLinkNode(StateNode *stateNode, LinkNode *list);
void deleteLinkNode(LinkNode *linkNodePrior);
StateNode *getTopStateNode(LinkNode *list);//返回list中的第一个结点,即fValue值最小的结点,并且将此节点从list中删除
int findLinkNodePrior(StateNode stateNode, LinkNode *list, LinkNode *linkNodePrior);//返回list中等于stateNode结点的前驱LinkNode结点
void spreadChildState(StateNode *childState, StateNode parentState, StateNode aimState, int direction);
StateNode *spread(StateNode *currentState, StateNode aimState, LinkNode *open, LinkNode *close);
void printSearchTrack(StateNode *goalState); //递归打印搜索过程
void processState(StateNode *startState, StateNode aimState);
#endif
文件:"TheEightDigits.cpp"
#include "h1.h"
void findSpace(StateNode currentState, int &x, int &y){ //Find the position of '0'.
int i, j;
for(i=0; i<3; i++){
for(j=0; j<3; j++){
if(currentState.grid[i][j] == 0){
x=i;
y=j;
break;
}
}
}
}
int calculateManhattanDistance(StateNode currentState, StateNode aimState, int value){ //Calculate the Manhattan Distance of two states.
int i, j, h1X, h1Y, h2X, h2Y;
for(i=0; i<3; i++){
for(j=0; j<3; j++){
if(currentState.grid[i][j]==value){
h1X = i;
h1Y = j;
}
if(aimState.grid[i][j]==value){
h2X = i;
h2Y = j;
}
}
}
return (abs(h1X-h2X)+abs(h1Y-h2Y));
}
int fValueCalculation(StateNode currentState, StateNode aimState){ //Calculate the f value of current state
int i, hValue;
hValue = 0;
for(i=1; i<=8; i++){
hValue += calculateManhattanDistance(currentState, aimState, i);
}
return hValue + currentState.gValue;
}
int isStatesEqual(StateNode state1, StateNode state2){ //Decide whether the two states equal, equal return 1, otherwise, return 0.
int i, j;
for(i=0; i<3; i++){
for(j=0; j<3; j++){
if(state1.grid[i][j] != state2.grid[i][j]){
return 0;
}
}
}
return 1;
}
int stateSpreadAble(StateNode currentState, int direction ){
int x, y;
findSpace(currentState, x, y);
if(x==0 && direction==1){ //direction=up
return 0;
}
if(x==2 && direction==2){ //direction=down
return 0;
}
if(y==0 && direction==3){ //direction=left
return 0;
}
if(y==2 && direction==4){ //direction=right
return 0;
}
return 1;
}
int stateTransformFeasible(StateNode startState, StateNode aimState){
int i, j, k;
int a, b;
int temp[9];
a = 0;
b = 0;
k = 0;
for(i=0; i<3; i++){
for(j=0; j<3; j++){
temp[k] = startState.grid[i][j];
k++;
}
}
for(i=1; i<9; i++){
for(j=0; j<i; j++){
if((temp[i]>temp[j]) && (temp[j]!=0)){
a++;
}
}
}
k = 0;
for(i=0; i<3; i++){
for(j=0; j<3; j++){
temp[k] = aimState.grid[i][j];
k++;
}
}
for(i=1; i<9; i++){
for(j=0; j<i; j++){
if((temp[i]>temp[j]) && (temp[j]!=0)){
b++;
}
}
}
if((a%2) == (b%2)){
return 1; //可解
}
else{
return 0; //不可解
}
}
void insertStateLinkNode(StateNode *stateNode, LinkNode *list){ //这里的插入节点是按照fValue的递增顺序排列的
//注意:tempNode的数据类型是LinkNode,tempState的数据类型是StateNode
LinkNode *insertNode;//待插入的LinkNode结点
LinkNode *tempNode; //暂存的LinkNode结点
LinkNode *priorNode; //用于插入的前驱LinkNode结点
priorNode = list;
tempNode = list->next;
while(tempNode && (tempNode->stateNode->fValue < stateNode->fValue)){ //找到插入insertNode的前驱结点
priorNode = priorNode->next;
tempNode = priorNode->next;
}
insertNode = (LinkNode *)malloc(sizeof(LinkNode));
if(!insertNode){
printf("Memory allocation error!\n");
exit(0);
}
insertNode->stateNode = stateNode;
insertNode->next = priorNode->next; //Insert "insertNode".
priorNode->next = insertNode;
}
void deleteLinkNode(LinkNode *linkNodePrior){ //释放需要释放的重复结点(这里的linkNode是重复结点的前驱结点)
LinkNode *tempNode;
tempNode = linkNodePrior->next;
linkNodePrior->next = tempNode->next;
free(tempNode);
}
StateNode *getTopStateNode(LinkNode *list){ //返回list中的第一个结点,即fValue值最小的结点,并且将此节点从list中删除
LinkNode *tempNode;
StateNode *tempState;
tempNode = list->next;
if(tempNode){
tempState = tempNode->stateNode;
list->next = tempNode->next;
return tempState;
}
else{
return NULL;
}
}
int findLinkNodePrior(StateNode stateNode, LinkNode *list, LinkNode *linkNodePrior){ //返回list中等于stateNode结点的前驱LinkNode结点
LinkNode *tempNode;
StateNode *tempState;
linkNodePrior = list;
tempNode = linkNodePrior->next;
while(tempNode){
tempState = tempNode->stateNode;
if(!isStatesEqual(stateNode, *tempState)){
linkNodePrior = linkNodePrior->next;
tempNode = linkNodePrior->next;
}
else{
return 1;
}
}
return 0;
}
void spreadChildState(StateNode *childState, StateNode parentState, StateNode aimState, int direction){
//扩展子节点
int i, j, x, y;
for(i=0; i<3; i++){
for(j=0; j<3; j++){
childState->grid[i][j] = parentState.grid[i][j];
}
}
findSpace(*childState, x, y);
switch(direction){
case 1: //up
childState->grid[x][y] = childState->grid[x-1][y];
childState->grid[x-1][y] = 0;
childState->fValue = fValueCalculation(*childState, aimState);
break;
case 2: //down
childState->grid[x][y] = childState->grid[x+1][y];
childState->grid[x+1][y] = 0;
childState->fValue = fValueCalculation(*childState, aimState);
break;
case 3: //left
childState->grid[x][y] = childState->grid[x][y-1];
childState->grid[x][y-1] = 0;
childState->fValue = fValueCalculation(*childState, aimState);
break;
case 4: //right
childState->grid[x][y] = childState->grid[x][y+1];
childState->grid[x][y+1] = 0;
childState->fValue = fValueCalculation(*childState, aimState);
break;
default:
break;
}
}
StateNode *spread(StateNode *currentState, StateNode aimState, LinkNode *open, LinkNode *close){
int i;
StateNode *childState;
StateNode *tempState;
LinkNode *tempNode;
LinkNode *linkNodePrior;
linkNodePrior = (LinkNode*)malloc(sizeof(LinkNode));
for(i=1; i<=4; i++){
if(stateSpreadAble(*currentState, i)){
childState = (StateNode *)malloc(sizeof(StateNode));//创建扩展子结点
childState->parent = currentState;
childState->gValue = currentState->gValue+1;
spreadChildState(childState, *currentState, aimState, i);
if(isStatesEqual(*childState, aimState)){
return childState;
}
if(findLinkNodePrior(*childState, close, linkNodePrior)){
continue;
}
else if(findLinkNodePrior(*childState, open, linkNodePrior)){
tempNode = linkNodePrior->next;
tempState = tempNode->stateNode;
if(tempState->fValue > childState->fValue){
deleteLinkNode(linkNodePrior);
insertStateLinkNode(childState, open);
}
}
else if(!findLinkNodePrior(*childState, open, linkNodePrior)){
insertStateLinkNode(childState, open);
}
}
}
return NULL;
}
void printSearchTrack(StateNode *goalState){ //递归打印搜索过程
int i, j;
if(goalState){
printSearchTrack(goalState->parent);
for(i=0; i<3; i++){
for(j=0; j<3; j++){
printf("%d ", goalState->grid[i][j]);
}
printf("\n");
}
printf("**************\n");
}
else{
return;
}
}
void processState(StateNode *startState, StateNode aimState){
LinkNode *open, *close;
StateNode *currentState;
StateNode *goalState;
open=(LinkNode*)malloc(sizeof(LinkNode));
if(!open){
printf("Memory allocation error!\n");
exit(0);
}
close=(LinkNode*)malloc(sizeof(LinkNode));
if(!close){
printf("Memory allocation error!\n");
exit(0);
}
open->next = NULL;
open->stateNode = NULL;
close->next = NULL;
close->stateNode = NULL;
insertStateLinkNode(startState, open);
currentState = getTopStateNode(open);
while(currentState){
insertStateLinkNode(currentState, close);//将即将处理的状态结点插入close表
goalState = spread(currentState, aimState, open, close);
if(goalState){
printf("Find a way !\n");
printSearchTrack(goalState);
return;
}
currentState = getTopStateNode(open);
}
}
int main(){
StateNode state = {{{4,1,2},{0,5,3},{7,8,6}},0,0,NULL};
StateNode aimState = {{{1,2,3},{4,5,6},{7,8,0}}, 0, 0, NULL};
StateNode* startState = &state;
if(!stateTransformFeasible(*startState, aimState)){
printf("The eight digits problem cannot be solved!\n");
exit(0);
}
if(!startState){
printf("Memory allocation error!\n");
exit(0);
}
startState->fValue = fValueCalculation(*startState, aimState);
processState(startState, aimState);
return 1;
}
注:测试数据,初始态: 4 1 2 终止态: 1 2 3
0 5 3 4 5 6
7 8 6 7 8 0
附:杭电 OJ 1043解题代码。
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<queue>
#include<stack>
using namespace std;
typedef struct{
int x;
int y;
}posNode;
typedef struct stateNode{
double fValue;
double gValue;
posNode pos;
int grid[3][3];
friend bool operator<(const stateNode &a, const stateNode &b){
return a.fValue > b.fValue;
}
}stateNode;
bool visited[363000];
int trace[363000];
char option[363000];
stack<char> stk;
const int fac[9] ={1, 1, 2, 6, 24, 120, 720, 5040, 720*56};
const int dir[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
const char direction[4] = {'d', 'r', 'u', 'l'};
int cantorValue(stateNode node){
int i, j, k, temp;
int res;
res = 0;
int a[9];
k = 0;
for(i=0; i<3; i++){
for(j=0; j<3; j++){
a[k++] = node.grid[i][j];
}
}
for(i=0; i<9; i++){
temp = 0;
for(j=i+1; j<9; j++){
if(a[j] < a[i]){
temp++;
}
}
res += temp*fac[8-i];
}
return res;
}
int hEstimate(stateNode cur, stateNode goal){
int i, j, k;
int temp;
int curX, curY;
int goalX, goalY;
int res;
int a[9] ={1, 2, 3, 4, 5, 6, 7, 8, 'x'};
res = 0;
curX = 0;
curY = 0;
goalX = 0;
goalY = 0;
for(i=0; i<9; i++){
if(a[i] == 'x'){
continue;
}
for(j=0; j<3; j++){
for(k=0; k<3; k++){
if(cur.grid[j][k] == a[i]){
curX = k;
curY = j;
break;
}
}
}
for(j=0; j<3; j++){
for(k=0; k<3; k++){
if(goal.grid[j][k] == a[i]){
goalX = k;
goalY = j;
break;
}
}
}
res += abs(curX - goalX) + abs(curY - goalY);
}
return res;
}
bool inversionCheck(stateNode cur, stateNode goal){
int i, j, k;
int temp[9];
int in1, in2;
k = 0;
for(i=0; i<3; i++){
for(j=0; j<3; j++){
temp[k++] = cur.grid[i][j];
}
}
in1 = 0;
for(i=0; i<9; i++){
if(temp[i] == 'x'){
continue;
}
for(j=i-1; j>=0; j--){
if(temp[j] == 'x'){
continue;
}
if(temp[j] > temp[i]){
in1++;
}
}
}
k = 0;
for(i=0; i<3; i++){
for(j=0; j<3; j++){
temp[k++] = goal.grid[i][j];
}
}
in2 = 0;
for(i=0; i<9; i++){
if(temp[i] == 'x'){
continue;
}
for(j=i-1; j>=0; j--){
if(temp[j] == 'x'){
continue;
}
if(temp[j] > temp[i]){
in2++;
}
}
}
if(in1%2 == in2%2){
return 1;
}
else{
return 0;
}
}
void AStar(stateNode start, stateNode goal){
int i, j, cantorVal;
priority_queue<stateNode> que;
memset(visited, false, sizeof(visited));
que.push(start);
while(!que.empty()){
stateNode cur = que.top();
que.pop();
for(i=0; i<4; i++){
int y, x;
y = cur.pos.y + dir[i][0];
x = cur.pos.x + dir[i][1];
if(x<0||x>=3||y<0||y>=3){
continue;
}
stateNode next(cur);
next.pos.y = y;
next.pos.x = x;
next.grid[cur.pos.y][cur.pos.x] = next.grid[y][x];
next.grid[y][x] = 'x';
cantorVal = cantorValue(next);
if(visited[cantorVal]){
continue;
}
visited[cantorVal] = true;
next.gValue++;
int h = hEstimate(next, goal);
next.fValue = next.gValue + hEstimate(next, goal);
int mm = cantorValue(cur);
trace[cantorVal] = cantorValue(cur);
option[cantorVal] = direction[i];
if(cantorVal == cantorValue(goal)){
return;
}
que.push(next);
}
}
}
void printTrace(int goal, int start){
while(goal != start){
stk.push(option[goal]);
goal = trace[goal];
}
while(!stk.empty()){
printf("%c", stk.top());
stk.pop();
}
printf("\n");
}
int main(){
int i, j, k;
char a[20];
stateNode start, goal;
for(i=0; i<3; i++){
for(j=0; j<=2; j++){
goal.grid[i][j] = i*3 + j+1;
}
}
goal.grid[2][2] = 'x';
goal.pos.x = 2;
goal.pos.y = 2;
while(gets(a) != NULL){
if(a[0] != 'x'){
start.grid[0][0] = a[0]-'0';
}
else{
start.grid[0][0] = a[0];
start.pos.x = 0;
start.pos.y = 0;
}
k = 2;
for(i=0; i<=2; i++){
for(j=0; j<=2; j++){
if(i==0 && j==0){
continue;
}
if(a[k] != 'x'){
start.grid[i][j] = a[k]-'0';
}
else{
start.grid[i][j] = a[k];
start.pos.y = i;
start.pos.x = j;
}
k += 2;
}
}
start.fValue = hEstimate(start, goal);
start.gValue = 0;
if(!inversionCheck(start, goal)){
printf("unsolvable\n");
continue;
}
AStar(start, goal);
printTrace(cantorValue(goal), cantorValue(start));
}
return 0;
}