一、引言
A算法是一种启发式搜索算法,常用于解决问题的最短路径。对于8数码问题,A算法可以用于找到从初始状态到目标状态的最短路径。
八数码问题是在 3×3
的九宫格棋盘上,摆有
8
个刻有
1
~
8
数码的将牌。棋盘中有一个格,允许紧邻空格的某一将牌可以移到空格中,这样通过平移将牌可以将某一将牌布局变换为另一布局。针对给定的一种初始布局或结构(目标状态),问如何移动将牌,实现从初始状态到目标状态的转变。如下图表示了一个具体的八数码问题求解。
二、代码
2.1 解决8数码问题
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define N 3
// 结点定义
typedef struct Node {
int puzzle[N][N];
struct Node* parent;
int cost; // g(n) - 实际代价
int heuristic; // h(n) - 启发式代价
} Node;
// 创建新结点
Node* createNode(int puzzle[N][N], int cost, int heuristic, Node* parent) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(EXIT_FAILURE);
}
// 复制拼图状态
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
newNode->puzzle[i][j] = puzzle[i][j];
}
}
newNode->parent = parent;
newNode->cost = cost;
newNode->heuristic = heuristic;
return newNode;
}
// 计算启发式代价(这里使用曼哈顿距离)
int calculateHeuristic(int puzzle[N][N], int goal[N][N]) {
int heuristic = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (puzzle[i][j] != 0) {
int goalX, goalY;
goalX = (puzzle[i][j] - 1) / N;
goalY = (puzzle[i][j] - 1) % N;
heuristic += abs(i - goalX) + abs(j - goalY);
}
}
}
return heuristic;
}
// 检查两个状态是否相同
bool areStatesEqual(int state1[N][N], int state2[N][N]) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (state1[i][j] != state2[i][j]) {
return false;
}
}
}
return true;
}
// 检查结点是否已经在开放列表中
bool isInOpenList(Node* node, Node** openList, int openListSize) {
for (int i = 0; i < openListSize; i++) {
if (areStatesEqual(node->puzzle, openList[i]->puzzle)) {
return true;
}
}
return false;
}
// 检查结点是否已经在关闭列表中
bool isInClosedList(Node* node, Node** closedList, int closedListSize) {
for (int i = 0; i < closedListSize; i++) {
if (areStatesEqual(node->puzzle, closedList[i]->puzzle)) {
return true;
}
}
return false;
}
// 查找空格位置
void findBlankPosition(int puzzle[N][N], int* blankX, int* blankY) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (puzzle[i][j] == 0) {
*blankX = i;
*blankY = j;
return;
}
}
}
}
// 移动方块
void move(int puzzle[N][N], int x1, int y1, int x2, int y2) {
int temp = puzzle[x1][y1];
puzzle[x1][y1] = puzzle[x2][y2];
puzzle[x2][y2] = temp;
}
// 生成子结点
Node* generateChildNode(Node* parent, int x1, int y1, int x2, int y2, int cost, int goal[N][N]) {
int childPuzzle[N][N];
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
childPuzzle[i][j] = parent->puzzle[i][j];
}
}
move(childPuzzle, x1, y1, x2, y2);
int heuristic = calculateHeuristic(childPuzzle, goal);
return createNode(childPuzzle, parent->cost + cost, heuristic, parent);
}
// 输出拼图状态
void printPuzzle(int puzzle[N][N]) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
printf("%d ", puzzle[i][j]);
}
printf("\n");
}
printf("\n");
}
// 查找可行的移动并生成子结点
void findAndGenerateChildren(Node* parent, Node** openList, int* openListSize,
Node** closedList, int* closedListSize, int goal[N][N]) {
int blankX, blankY;
findBlankPosition(parent->puzzle, &blankX, &blankY);
// 向上移动
if (blankX > 0) {
Node* childNode = generateChildNode(parent, blankX, blankY, blankX - 1, blankY, 1, goal);
if (!isInClosedList(childNode, closedList, *closedListSize) &&
!isInOpenList(childNode, openList, *openListSize)) {
openList[*openListSize] = childNode;
(*openListSize)++;
} else {
free(childNode);
}
}
// 向下移动
if (blankX < N - 1) {
Node* childNode = generateChildNode(parent, blankX, blankY, blankX + 1, blankY, 1, goal);
if (!isInClosedList(childNode, closedList, *closedListSize) &&
!isInOpenList(childNode, openList, *openListSize)) {
openList[*openListSize] = childNode;
(*openListSize)++;
} else {
free(childNode);
}
}
// 向左移动
if (blankY > 0) {
Node* childNode = generateChildNode(parent, blankX, blankY, blankX, blankY - 1, 1, goal);
if (!isInClosedList(childNode, closedList, *closedListSize) &&
!isInOpenList(childNode, openList, *openListSize)) {
openList[*openListSize] = childNode;
(*openListSize)++;
} else {
free(childNode);
}
}
// 向右移动
if (blankY < N - 1) {
Node* childNode = generateChildNode(parent, blankX, blankY, blankX, blankY + 1, 1, goal);
if (!isInClosedList(childNode, closedList, *closedListSize) &&
!isInOpenList(childNode, openList, *openListSize)) {
openList[*openListSize] = childNode;
(*openListSize)++;
} else {
free(childNode);
}
}
}
// 计算逆序数
int countInversions(int puzzle[N][N]) {
int inversions = 0;
int flatArray[N * N];
// 将二维数组展平成一维数组
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
flatArray[i * N + j] = puzzle[i][j];
}
}
// 计算逆序数
for (int i = 0; i < N * N - 1; i++) {
for (int j = i + 1; j < N * N; j++) {
if (flatArray[i] > flatArray[j] && flatArray[i] != 0 && flatArray[j] != 0) {
inversions++;
}
}
}
return inversions;
}
// A*算法核心函数
void aStarAlgorithm(Node* initialState, int goal[N][N]) {
Node* openList[N * N * N * N]; // 最坏情况下可能有 N^4 个结点
int openListSize = 0;
Node* closedList[N * N * N * N];
int closedListSize = 0;
openList[openListSize++] = initialState;
while (openListSize > 0) {
Node* currentNode = openList[0];
int currentIndex = 0;
// 在开放列表中找到启发式代价最小的结点
for (int i = 1; i < openListSize; i++) {
if ((currentNode->cost + currentNode->heuristic) > (openList[i]->cost + openList[i]->heuristic)) {
currentNode = openList[i];
currentIndex = i;
}
}
// 将当前结点从开放列表中移除
for (int i = currentIndex; i < openListSize - 1; i++) {
openList[i] = openList[i + 1];
}
openListSize--;
// 将当前结点添加到关闭列表中
closedList[closedListSize++] = currentNode;
// 如果当前结点达到目标状态,结束算法
if (areStatesEqual(currentNode->puzzle, goal)) {
// Output the path from the goal to the initial state
printf("Goal state reached!\n");
Node* path[N * N * N * N]; // 用于存储路径上的节点
int pathSize = 0;
while (currentNode != NULL) {
path[pathSize++] = currentNode;
currentNode = currentNode->parent;
}
// Reverse the path (to get it from initial to goal)
for (int i = pathSize - 1; i >= 0; i--) {
for (int j = 0; j < N; j++) {
for (int k = 0; k < N; k++) {
printf("%d ", path[i]->puzzle[j][k]);
}
printf("\n");
}
printf("\n");
}
printf("Shortest path:\n");
for (int i = pathSize - 1; i >= 0; i--) {
printf("Step %d:\n", pathSize - i - 1);
printPuzzle(path[i]->puzzle); // Use the printPuzzle function to print the puzzle configuration
printf("\n");
}
// 释放内存
for (int i = 0; i < closedListSize; i++) {
free(closedList[i]);
}
return;
}
// 生成子结点并添加到开放列表中
findAndGenerateChildren(currentNode, openList, &openListSize, closedList, &closedListSize, goal);
}
// 释放内存
for (int i = 0; i < closedListSize; i++) {
free(closedList[i]);
}
}
int main() {
int initial[N][N] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; // 初始状态
printf("请输入初始状态S:");
for(int i=0; i<N; i++){
for(int j=0; j<N; j++){
scanf("%d,", &initial[i][j]);
}
}
int goal[N][N] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; // 目标状态
printf("请输入终点状态S:");
for(int i=0; i<N; i++){
for(int j=0; j<N; j++){
scanf("%d,", &goal[i][j]);
}
}
int inversions = countInversions(initial);
int goalInversions = countInversions(goal);
// 检查逆序数奇偶性
if ((inversions % 2 == 0 && goalInversions % 2 == 0) || (inversions % 2 == 1 && goalInversions % 2 == 1)) {
printf("问题有解\n");
Node* initialState = createNode(initial, 0, calculateHeuristic(initial, goal), NULL);
aStarAlgorithm(initialState, goal);
} else {
printf("初始状态的逆序对的数量为%d, 目标状态的逆序对的数量为%d\n", inversions, goalInversions);
printf("问题无解\n");
return 0;
}
Node* initialState = createNode(initial, 0, calculateHeuristic(initial, goal), NULL);
aStarAlgorithm(initialState, goal);
return 0;
}
2.2 解决15数码问题
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define N 4
// 结点定义
typedef struct Node {
int puzzle[N][N];
struct Node* parent;
int cost; // g(n) - 实际代价
int heuristic; // h(n) - 启发式代价
} Node;
// 创建新结点
Node* createNode(int puzzle[N][N], int cost, int heuristic, Node* parent) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("Memory allocation failed.\n");
exit(EXIT_FAILURE);
}
// 复制拼图状态
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
newNode->puzzle[i][j] = puzzle[i][j];
}
}
newNode->parent = parent;
newNode->cost = cost;
newNode->heuristic = heuristic;
return newNode;
}
// 计算启发式代价(这里使用曼哈顿距离)
int calculateHeuristic(int puzzle[N][N], int goal[N][N]) {
int heuristic = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (puzzle[i][j] != 0) {
int goalX, goalY;
goalX = (puzzle[i][j] - 1) / N;
goalY = (puzzle[i][j] - 1) % N;
heuristic += abs(i - goalX) + abs(j - goalY);
}
}
}
return heuristic;
}
// 检查两个状态是否相同
bool areStatesEqual(int state1[N][N], int state2[N][N]) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (state1[i][j] != state2[i][j]) {
return false;
}
}
}
return true;
}
// 检查结点是否已经在开放列表中
bool isInOpenList(Node* node, Node** openList, int openListSize) {
for (int i = 0; i < openListSize; i++) {
if (areStatesEqual(node->puzzle, openList[i]->puzzle)) {
return true;
}
}
return false;
}
// 检查结点是否已经在关闭列表中
bool isInClosedList(Node* node, Node** closedList, int closedListSize) {
for (int i = 0; i < closedListSize; i++) {
if (areStatesEqual(node->puzzle, closedList[i]->puzzle)) {
return true;
}
}
return false;
}
// 查找空格位置
void findBlankPosition(int puzzle[N][N], int* blankX, int* blankY) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (puzzle[i][j] == 0) {
*blankX = i;
*blankY = j;
return;
}
}
}
}
// 移动方块
void move(int puzzle[N][N], int x1, int y1, int x2, int y2) {
int temp = puzzle[x1][y1];
puzzle[x1][y1] = puzzle[x2][y2];
puzzle[x2][y2] = temp;
}
// 生成子结点
Node* generateChildNode(Node* parent, int x1, int y1, int x2, int y2, int cost, int goal[N][N]) {
int childPuzzle[N][N];
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
childPuzzle[i][j] = parent->puzzle[i][j];
}
}
move(childPuzzle, x1, y1, x2, y2);
int heuristic = calculateHeuristic(childPuzzle, goal);
return createNode(childPuzzle, parent->cost + cost, heuristic, parent);
}
// 输出拼图状态
void printPuzzle(int puzzle[N][N]) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
printf("%d ", puzzle[i][j]);
}
printf("\n");
}
printf("\n");
}
// 查找可行的移动并生成子结点
void findAndGenerateChildren(Node* parent, Node** openList, int* openListSize,
Node** closedList, int* closedListSize, int goal[N][N]) {
int blankX, blankY;
findBlankPosition(parent->puzzle, &blankX, &blankY);
// 向上移动
if (blankX > 0) {
Node* childNode = generateChildNode(parent, blankX, blankY, blankX - 1, blankY, 1, goal);
if (!isInClosedList(childNode, closedList, *closedListSize) &&
!isInOpenList(childNode, openList, *openListSize)) {
openList[*openListSize] = childNode;
(*openListSize)++;
} else {
free(childNode);
}
}
// 向下移动
if (blankX < N - 1) {
Node* childNode = generateChildNode(parent, blankX, blankY, blankX + 1, blankY, 1, goal);
if (!isInClosedList(childNode, closedList, *closedListSize) &&
!isInOpenList(childNode, openList, *openListSize)) {
openList[*openListSize] = childNode;
(*openListSize)++;
} else {
free(childNode);
}
}
// 向左移动
if (blankY > 0) {
Node* childNode = generateChildNode(parent, blankX, blankY, blankX, blankY - 1, 1, goal);
if (!isInClosedList(childNode, closedList, *closedListSize) &&
!isInOpenList(childNode, openList, *openListSize)) {
openList[*openListSize] = childNode;
(*openListSize)++;
} else {
free(childNode);
}
}
// 向右移动
if (blankY < N - 1) {
Node* childNode = generateChildNode(parent, blankX, blankY, blankX, blankY + 1, 1, goal);
if (!isInClosedList(childNode, closedList, *closedListSize) &&
!isInOpenList(childNode, openList, *openListSize)) {
openList[*openListSize] = childNode;
(*openListSize)++;
} else {
free(childNode);
}
}
}
// 计算逆序数
int countInversions(int puzzle[N][N]) {
int inversions = 0;
int flatArray[N * N];
// 将二维数组展平成一维数组
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
flatArray[i * N + j] = puzzle[i][j];
}
}
// 计算逆序数
for (int i = 0; i < N * N - 1; i++) {
for (int j = i + 1; j < N * N; j++) {
if (flatArray[i] > flatArray[j] && flatArray[i] != 0 && flatArray[j] != 0) {
inversions++;
}
}
}
return inversions;
}
// A*算法核心函数
void aStarAlgorithm(Node* initialState, int goal[N][N]) {
Node* openList[N * N * N * N]; // 最坏情况下可能有 N^4 个结点
int openListSize = 0;
Node* closedList[N * N * N * N];
int closedListSize = 0;
openList[openListSize++] = initialState;
while (openListSize > 0) {
Node* currentNode = openList[0];
int currentIndex = 0;
// 在开放列表中找到启发式代价最小的结点
for (int i = 1; i < openListSize; i++) {
if ((currentNode->cost + currentNode->heuristic) > (openList[i]->cost + openList[i]->heuristic)) {
currentNode = openList[i];
currentIndex = i;
}
}
// 将当前结点从开放列表中移除
for (int i = currentIndex; i < openListSize - 1; i++) {
openList[i] = openList[i + 1];
}
openListSize--;
// 将当前结点添加到关闭列表中
closedList[closedListSize++] = currentNode;
// 如果当前结点达到目标状态,结束算法
if (areStatesEqual(currentNode->puzzle, goal)) {
// Output the path from the goal to the initial state
printf("Goal state reached!\n");
Node* path[N * N * N * N]; // 用于存储路径上的节点
int pathSize = 0;
while (currentNode != NULL) {
path[pathSize++] = currentNode;
currentNode = currentNode->parent;
}
// Reverse the path (to get it from initial to goal)
for (int i = pathSize - 1; i >= 0; i--) {
for (int j = 0; j < N; j++) {
for (int k = 0; k < N; k++) {
printf("%d ", path[i]->puzzle[j][k]);
}
printf("\n");
}
printf("\n");
}
printf("Shortest path:\n");
for (int i = pathSize - 1; i >= 0; i--) {
printf("Step %d:\n", pathSize - i - 1);
printPuzzle(path[i]->puzzle); // Use the printPuzzle function to print the puzzle configuration
printf("\n");
}
// 释放内存
for (int i = 0; i < closedListSize; i++) {
free(closedList[i]);
}
return;
}
// 生成子结点并添加到开放列表中
findAndGenerateChildren(currentNode, openList, &openListSize, closedList, &closedListSize, goal);
}
// 释放内存
for (int i = 0; i < closedListSize; i++) {
free(closedList[i]);
}
}
int main() {
int initial[N][N] = {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}; // 初始状态
printf("请输入初始状态S:");
for(int i=0; i<N; i++){
for(int j=0; j<N; j++){
scanf("%d,", &initial[i][j]);
}
}
int goal[N][N] = {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}; // 目标状态
printf("请输入终点状态S:");
for(int i=0; i<N; i++){
for(int j=0; j<N; j++){
scanf("%d,", &goal[i][j]);
}
}
int inversions = countInversions(initial);
int goalInversions = countInversions(goal);
// 检查逆序数奇偶性
if ((inversions % 2 == 0 && goalInversions % 2 == 0) || (inversions % 2 == 1 && goalInversions % 2 == 1)) {
printf("问题有解\n");
Node* initialState = createNode(initial, 0, calculateHeuristic(initial, goal), NULL);
aStarAlgorithm(initialState, goal);
} else {
printf("初始状态的逆序对的数量为%d, 目标状态的逆序对的数量为%d\n", inversions, goalInversions);
printf("问题无解\n");
return 0;
}
Node* initialState = createNode(initial, 0, calculateHeuristic(initial, goal), NULL);
aStarAlgorithm(initialState, goal);
return 0;
}