前言:
A*算法的原理网上有很多,文章都讲的非常详细,我就不再多说了。
简单分享一下学习的体验:
思路必须清晰,不然会写的很痛苦。我写这个代码花费了很长时间,大部分时间花在了构思和调试上。
先说构思。我在看过A*算法解决八数码问题的原理之后,第一时间想:
结构体实现八数码,优先队列实现OPEN,CLOSE表,整体流程就是初始化,取OEPN表结点,移动派生子节点,该结点添加到CLOSE表,循环往复,直至移动到正确位置。
但是问题出在哪了呢?
八数码生成树的整体结构我没弄清楚。所以一开始我写的结构体他就不适合这个树,不适合去构造最优解。虽然整体流程我能说个差不多的意思,但落实到代码里,各种细节,特别是条件语句,这个条件我是没弄明白的,最后我写出来的代码求不出来答案。
本来打算复制粘贴了,去网上翻了翻代码,但是自己心有不甘,明明付出了这么多都要白费了吗,又开始写了起来。网上大牛写的代码确实好,看了之后自己的脑子清晰了很多。
后来我做的最多的工作就是拆分和细化,我知道具体这一部分是做什么用的,组合起来有实现什么功能,整个思路就丰富了,代码里该有的部分都补充和优化好了。
然后说说调试出现的问题:
首先是指针,这个每次都要调试。然后就是各种操作函数的调试,写代码最怕的就是看不见的那一部分,和你预期的不一样很难发现错误,先保证各种函数是正确的再调用。
这一次出的最大错误是在优先队列自定义排序,我用的是结构体指针,正确的做法是写一个自己的compare,可以用结构体写,里面实现对结构体的排序。但起初我不知道,我用了重载运算符,重载运算符结构体可以用,但是指针类型不行,我花了很长时间才找到这个错误。
最后也是顺利的写出来了,简单样例可以跑出来结果,现在觉得也不是那么复杂。
不过自己随便输的样例还是求不出来,算了,我已经停止思考了......
测试代码:
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
/*-----------------------------------------目标八数码*/
int recell[3][3] = {
1, 2, 3,
8, 0, 4,
7, 6, 5
};
int res_pos_x(int num) { //返回数字的索引
int x = 0;
switch (num) {
case 1: x = 0; break;
case 2: x = 0; break;
case 3: x = 0; break;
case 4: x = 1; break;
case 5: x = 2; break;
case 6: x = 2; break;
case 7: x = 2; break;
case 8: x = 1; break;
case 0: x = 1; break;
}
return x;
}
int res_pos_y(int num) {
int y = 0;
switch (num) {
case 1: y = 0; break;
case 2: y = 1; break;
case 3: y = 2; break;
case 4: y = 2; break;
case 5: y = 2; break;
case 6: y = 1; break;
case 7: y = 0; break;
case 8: y = 0; break;
case 0: y = 1; break;
}
return y;
}
/*---------------------------------------------------*/
/*-------------------------------------------数据结构*/
#define CELL_SIZE 3 //八数码尺寸
#define DIRECT_SIZE 4 //可移动方向
int MAX_ITER_SIZE; //最大计算次数
#define UP 0
#define RIGHT 1
#define DOWN 2
#define LEFT 3
int measure; //计算方法标志
typedef struct node { //八数码结点结构体
int pos_0[2]; //0的位置
int cell[CELL_SIZE][CELL_SIZE]; //八数码数组
int f, g, h; //启发式函数变量
struct node* pre; //父节点指针
int getf(int level) { //启发式函数 计算并返回f(x)
g = level; //深度作为g(x)
h = 0;
for (int i = 0; i < CELL_SIZE; i++) {
for (int j = 0, num; j < CELL_SIZE; j++) {
num = cell[i][j];
switch (measure) { //计算过程中忽略0的影响
case 1: //不在位个数
h += (num == 0) ? 0 : ((i == res_pos_x(num) && j == res_pos_y(num)) ? 0 : 1);
break;
case 2: //曼哈顿距离
h += (num == 0) ? 0 : (abs(i - res_pos_x(num)) + abs(j - res_pos_y(num)));
break;
case 3: //直线距离
h += (num == 0) ? 0 : (int)sqrt((i - res_pos_x(num) * (i - res_pos_x(num)) + (j - res_pos_y(num)) * (j - res_pos_y(num))));
break;
default: //默认 不在位个数
h += (num == 0) ? 0 : ((i == res_pos_x(num) && j == res_pos_y(num)) ? 0 : 1);
break;
}
}
}
f = g + h;
return f;
}
void getCell() { //打印结点基本数据
for (int i = 0; i < CELL_SIZE; i++) {
for (int j = 0; j < CELL_SIZE; j++) {
cout << cell[i][j] << " ";
}
cout << endl;
}
cout << "f:" << f << " g:" << g << " h:" << h << endl;
}
}*eight_puzzle;
//优先队列实现OPEN表 重写比较函数实现优先级
struct cmp{
bool operator() (const eight_puzzle a, const eight_puzzle b){
return a->f > b->f;
}
};
priority_queue<eight_puzzle, vector<eight_puzzle>, cmp> open;
vector<eight_puzzle> close;
int lo, //OPEN表长度
lc; //CLOSE表长度
//定义起始结点和目标结点
eight_puzzle start = (eight_puzzle)malloc(sizeof(struct node));
eight_puzzle target = (eight_puzzle)malloc(sizeof(struct node));
/*---------------------------------------------------*/
/*-----------------------------------------------函数*/
void swap(int* a, int* b); //交换数字 a 和 b
void copy(eight_puzzle a, eight_puzzle b); //复制结点 b 到 a
bool isequal(eight_puzzle a, eight_puzzle b); //比较结点 a 和 b
void concat(int a[CELL_SIZE][CELL_SIZE], //复制结点 a 的数组 到 b
const int b[CELL_SIZE][CELL_SIZE]);
void move(eight_puzzle a); //以当前结点 a 为扩展结点进行移动
bool isclosed(eight_puzzle a); //结点是否在CLOSE表中
void init(); //初始化 起始结点 和 目标结点
void backtravel(eight_puzzle a); //回溯遍历 输出最优解
void swap(int* a, int* b) {
int *t = a;
a = b;
b = t;
}
void copy(eight_puzzle a, const eight_puzzle b) {
a->f = b->f;
a->g = b->g;
a->h = b->h;
a->pos_0[0] = b->pos_0[0];
a->pos_0[1] = b->pos_0[1];
concat(a->cell, b->cell);
}
bool isequal(eight_puzzle a, eight_puzzle b) {
for (int i = 0; i < CELL_SIZE; i++) {
for (int j = 0; j < CELL_SIZE; j++) {
if (a->cell[i][j] != b->cell[i][j]) return false;
}
}
return true;
}
void concat(int a[CELL_SIZE][CELL_SIZE], const int b[CELL_SIZE][CELL_SIZE]) {
for (int i = 0; i < CELL_SIZE; i++) {
for (int j = 0; j < CELL_SIZE; j++) {
a[i][j] = b[i][j];
}
}
}
void move(eight_puzzle a) {
/*
循环 四个方向{
临时结点 t
复制结点 a 到 t
如果 0 能移动{
临时结点 t 移动
临时结点 t 父节点指向 a
添加 临时结点 t 到 OPEN表
}
}
*/
for(int i, j, d = 0; d < DIRECT_SIZE; d++){
eight_puzzle t = (eight_puzzle)malloc(sizeof(struct node));
copy(t, a);
i = t->pos_0[0];
j = t->pos_0[1];
switch (d) {
case UP:
if (i - 1 > -1) {
swap(t->cell[i][j], t->cell[i - 1][j]);
t->pos_0[0] = i - 1;
}
break;
case RIGHT:
if (j + 1 < CELL_SIZE) {
swap(t->cell[i][j], t->cell[i][j + 1]);
t->pos_0[1] = j + 1;
}
break;
case DOWN:
if (i + 1 < CELL_SIZE) {
swap(t->cell[i][j], t->cell[i + 1][j]);
t->pos_0[0] = i + 1;
}
break;
case LEFT:
if (j - 1 > -1) {
swap(t->cell[i][j], t->cell[i][j - 1]);
t->pos_0[1] = j - 1;
}
break;
}
t->pre = a;
t->getf(a->g + 1);
open.push(t); lo++;
}
}
bool isclosed(eight_puzzle a) {
for (int i = 0; i < lc; i++) {
if (isequal(a, close.at(i))) return true;
}
return false;
}
void init() {
start->f = start->g = start->h = 0;
start->pre = NULL;
for (int i = 0; i < CELL_SIZE; i++) {
for (int j = 0; j < CELL_SIZE; j++) {
cin >> start->cell[i][j];
if (start->cell[i][j] == 0) {
start->pos_0[0] = i;
start->pos_0[1] = j;
}
}
}
target->f = target->g = target->h = 0;
target->pre = NULL;
concat(target->cell, recell);
lo = lc = 0;
start->getf(0);
return;
}
void backtravel(eight_puzzle a) {
if (a) {
backtravel(a->pre);
cout << endl << "第 " << a->g << " 步" << endl;
cout << "------" << endl;
a->getCell();
cout << "------" << endl << endl;
}
}
/*---------------------------------------------------*/
int main() {
int output, cnt = 0;
cout << "输入1开启(0关闭) 当前结点八数码 显示" << endl;
cin >> output;
cout << endl;
cout << "请输入最大计算次数" << endl;
cin >> MAX_ITER_SIZE;
cout << endl;
cout << "请选择启发式函数h(x)的计算方法" << endl;
cout << "输入 1 计算不在位个数" << endl;
cout << "输入 2 计算曼哈顿距离" << endl;
cout << "输入 3 计算直线距离" << endl;
cin >> measure;
cout << endl;
cout << "请输入起始八数码" << endl;
init();
open.push(start); lo++;
eight_puzzle cur = (eight_puzzle)malloc(sizeof(struct node));
while (lo > 0 && lc < MAX_ITER_SIZE) {
cnt++;
cur = open.top(); open.pop(); lo--;
if (output) {
cout << endl << "------" << endl;
cur->getCell();
cout << "------" << endl << endl;
}
if (isequal(cur, target)) {
cout << "寻找到答案!!!" << endl;
backtravel(cur);
cout << "本次计算中0移动了" << cnt - 1 << "次" << endl;
system("pause");
return 0;
}
close.push_back(cur); lc++;
move(cur);
}
cout << "在一定限度内未能寻找到答案" << endl;
system("pause");
return 0;
}