当前位置:   article > 正文

A*算法解决八数码问题(C语言实现)_八数码问题a*算法c语言

八数码问题a*算法c语言

准备两个表

        待计算的节点保存在一个open表中,已经计算过的节点则保存在一个closed表中,其中的open表用一个优先队列表示,以使那些未访问过的节点可以按照其估价函数值的顺序出列。

估价函数

f(n) = g(n) + h(n)

        g(n)为实际成本,在这里理解为深度depth,h(n)为启发式函数,在这里理解为当前节点相对目标矩阵节点的元素偏移量,即有多少个节点不在目标位置。

估价函数的计算

        在这里用一维数组表示矩阵:右图为目标矩阵节点。

        左图的g(n)为此时矩阵节点所在的深度,根节点的深度为0。

        h(n)为8,因为有8个数字不在目标位置。

想法

        初始矩阵变换到目标矩阵的过程,是一个数字移动的过程,相对来说,也是元素0移动的过程,元素0经过向四个方向的多次移动,最终到达了目标节点。

        当初始矩阵与目标矩阵逆序数的奇偶性不相同的时候,节点不可达

        目标节点:{1,2,3,4,5,6,7,8,0},逆序数为8

        其实节点:{1,6,5,3,8,2,7,4,0},逆序数为20

        两个节点逆序数奇偶性相同,可达。

核心算法astar伪代码

  1. 初始化open表、close
  2. 将初始节点加入open
  3. while(open表不为空) {
  4. open表中取出最佳节点node
  5. if(node为目标节点) {
  6. 打印移动过程,循环结束
  7. } else {
  8. for(扩展node所有的邻接节点child) {
  9. if(child存在于close表) {
  10. 抛弃child,进入下一次循环
  11. }
  12. 更新child节点的属性,包括pred、g、h、depth、blank
  13. if(child存在于open表) {
  14. 留下最优的节点
  15. } else {
  16. 直接将child加入open
  17. }
  18. }
  19. }
  20. }

代码(C语言实现)

头文件

  1. #include <stdio.h>
  2. #include <malloc.h>
  3. #include <stdlib.h>
  4. #include <time.h>
  5. #define MAX_BOARD 9
  6. #define MAX_LIST_ELEMENTS 100000
  7. #define ALPHA (double)1.0
  8. #define BETA (double)2.0
  9. #define MAX_DEPTH 26
  10. #define MAX_VECTOR 4
  11. #define RANDINIT() srand(time(NULL))
  12. #define listCount(x) ((x)->numElements)
  13. #define RANDOM() ((float)rand() / (float)RAND_MAX)
  14. #define RANDMAX(x) (int)((float)(x)*rand()/(RAND_MAX+1.0))

结构体定义

        定义矩阵节点board_t,列表list_t,在扩展时使用到的状态节点

  1. typedef struct board_s {
  2. //前驱节点
  3. struct board_s *pred;
  4. //f = h + g
  5. int f;
  6. int g;
  7. int h;
  8. char array[MAX_BOARD];
  9. char blank;
  10. char depth;
  11. }board_t;
  12. typedef struct {
  13. //元素个数
  14. int numElements;
  15. //存放指向 board_t 结构体类型的指针
  16. board_t *elements[MAX_LIST_ELEMENTS];
  17. }list_t;
  18. typedef struct {
  19. int len;//当前节点0能移动的方向总数
  20. int vector[MAX_VECTOR];//0能到达的位置
  21. } move_t;

初始化表

  1. //创建open表和close表
  2. list_t openList_p;
  3. list_t closedList_p;
  4. //完成表的初始化
  5. void initList(list_t *list_p) {
  6. int i;
  7. list_p->numElements = 0;
  8. for(i = 0; i < MAX_LIST_ELEMENTS; i++) {
  9. list_p->elements[i] = (board_t*)0;
  10. }
  11. return;
  12. }

初始化可达节点

  1. void pri(board_t *child_p) {
  2. if(child_p != (board_t *)0) {
  3. printf("f = %d\n", child_p->f);
  4. printf("g = %d\n", child_p->g);
  5. printf("h = %d\n", child_p->h);
  6. printf("blank = %d\n", child_p->blank);
  7. printf("depth = %d\n", child_p->depth);
  8. printf("\n");
  9. }
  10. }
  11. //矩阵节点的初始化
  12. board_t *nodeAlloc() {
  13. board_t *board_p;
  14. board_p = (board_t*)malloc(sizeof(board_t));
  15. //当前状态的前驱默认为空
  16. board_p->pred = NULL;
  17. //fgh初始化为0
  18. board_p->f = board_p->g = board_p->h = 0;
  19. return board_p;
  20. }
  21. //返回逆序数
  22. int countInversions(char *array) {
  23. int i, j, inversions = 0;
  24. for(i = 0; i < MAX_BOARD - 1; i++) {
  25. for(j = i + 1; j < MAX_BOARD; j++) {
  26. if(array[i] > array[j]) {
  27. inversions++;
  28. }
  29. }
  30. }
  31. return inversions;
  32. }
  33. //计算位偏移量
  34. int evaluateBoard(board_t *board_p) {
  35. int i;
  36. const int test[MAX_BOARD - 1] = {1,2,3,4,5,6,7,8};
  37. int score = 0;
  38. for(i = 0; i < MAX_BOARD - 1; i++) {
  39. if(board_p->array[i] != test[i]) {
  40. score++;
  41. }
  42. }
  43. return score;
  44. }
  45. //随机生成节点,直到节点可达然后返回
  46. board_t *initPuzzle() {
  47. int i, inversions;
  48. board_t *board_p;
  49. board_p = nodeAlloc();
  50. //矩阵初始化为123456780
  51. for(i = 0; i < MAX_BOARD - 1; i++) {
  52. board_p->array[i] = i + 1;
  53. }
  54. board_p->array[i] = 0;
  55. //开始循环,在和1按位与为0的时候结束循环。
  56. do {
  57. //生成0-9的随机数,将矩阵打乱
  58. for(i = 0; i < MAX_BOARD; i++) {
  59. int x = RANDMAX(MAX_BOARD);
  60. int y = RANDMAX(MAX_BOARD);
  61. int temp = board_p->array[x];
  62. board_p->array[x] = board_p->array[y];
  63. board_p->array[y] = temp;
  64. }
  65. for(int k = 0; k < MAX_BOARD; k++) {
  66. printf("%d ", board_p->array[k]);
  67. }
  68. printf("\n");
  69. //得到inversions,在inversion
  70. inversions = countInversions(board_p->array);
  71. } while(inversions & 1);
  72. //找0的位置
  73. for(i = 0; i < MAX_BOARD; i++) {
  74. if(board_p->array[i] == 0) {
  75. board_p->blank = i;
  76. break;
  77. }
  78. }
  79. board_p->f = board_p->h = evaluateBoard(board_p);
  80. board_p->depth = board_p->g = 0;
  81. return board_p;
  82. }

将节点加入表

  1. //向list队列中加入board_t节点
  2. void putList(list_t *list_p, board_t *board_p) {
  3. for(int i = 0; i < MAX_LIST_ELEMENTS; i++) {
  4. if(list_p->elements[i] == (board_t*)0) {
  5. list_p->elements[i] = board_p;
  6. list_p->numElements++;
  7. return;
  8. }
  9. }
  10. }

astar

  1. void nodeFree(board_t *board_p) {
  2. free(board_p);
  3. return;
  4. }
  5. //找到f最小的节点,将该节点保存并返回,然后将该位置的值置为零
  6. board_t *getListBest(list_t *list_p)
  7. {
  8. int i, first=1;
  9. int best=-1;
  10. double best_f;
  11. board_t *board_p;
  12. for (i = 0; i < MAX_LIST_ELEMENTS; i++) {
  13. if (list_p->elements[i]) {
  14. if (first) {
  15. best = i;
  16. best_f = list_p->elements[best]->f;
  17. first = 0;
  18. } else {
  19. //若节点重复出现,当前节点的f < 最好节点的 f,发生替换
  20. if (list_p->elements[i]->f < best_f) {
  21. best = i;
  22. best_f = list_p->elements[best]->f;
  23. }
  24. }
  25. }
  26. }
  27. board_p = list_p->elements[best];
  28. list_p->elements[best] = (board_t *)0;
  29. list_p->numElements--;
  30. return board_p;
  31. }
  32. //判断当前节点是否在表中,其中的pos为指向int类型的指针变量,若节点存在于表中,使用pos返回存储的位置
  33. int onList(list_t *list_p, char *board_p, int *pos) {
  34. int i, j;
  35. for(i = 0; i < MAX_LIST_ELEMENTS; i++) {
  36. //剪枝
  37. if(list_p->elements[i] != (board_t*)0) {
  38. for(j = 0; j < MAX_BOARD; j++) {
  39. //比较,若所有元素都相同则认为存在于表中
  40. if(list_p->elements[i]->array[j] != board_p[j]) {
  41. break;
  42. }
  43. }
  44. if(j == MAX_BOARD) {
  45. if(pos)
  46. *pos = i;
  47. return 1;
  48. }
  49. }
  50. }
  51. return 0;
  52. }
  53. board_t *getList(list_t *list_p, char *board_p) {
  54. int pos, ret;
  55. board_t *retboard_p = (board_t*)0;
  56. ret = onList(list_p, board_p, &pos);
  57. //如果存在于该队列中,
  58. if(ret) {
  59. retboard_p = list_p->elements[pos];
  60. list_p->elements[pos] = (board_t *)0;
  61. list_p->numElements--;
  62. }
  63. return retboard_p;
  64. }
  65. //元素0在每个位置能够移动的方向,如第一行,在下标为0的位置有两个方向可以移动,分别为1和3
  66. const move_t moves[MAX_BOARD] = {
  67. /* 0 */ { 2, {1, 3} },
  68. /* 1 */ { 3, {0, 2, 4} },
  69. /* 2 */ { 2, {1, 5} },
  70. /* 3 */ { 3, {0, 4, 6} },
  71. /* 4 */ { 4, {1, 3, 5, 7} },
  72. /* 5 */ { 3, {2, 4, 8} },
  73. /* 6 */ { 2, {3, 7} },
  74. /* 7 */ { 3, {4, 6, 8} },
  75. /* 8 */ { 2, {5, 7} } };
  76. board_t *getChildBoard(board_t *board_p, int index) {
  77. board_t *child_p = (board_t *)0;
  78. int i;
  79. //当前节点0所在的位置
  80. int blankSpot = board_p->blank;
  81. //index条件成立时,表示该方向可走。
  82. if(index < moves[blankSpot].len) {
  83. child_p = nodeAlloc();
  84. //将元节点进行拷贝,用于位置的移动
  85. for(i = 0; i < MAX_BOARD; i++) {
  86. child_p->array[i] = board_p->array[i];
  87. }
  88. child_p->blank = board_p->blank;
  89. //moveFrom表示0移动到的下一个位置
  90. int moveFrom = moves[blankSpot].vector[index];
  91. //位置移动
  92. child_p->array[(int)child_p->blank] = child_p->array[moveFrom];
  93. child_p->array[moveFrom] = 0;
  94. //更新0的位置
  95. child_p->blank = moveFrom;
  96. }
  97. // pri(child_p);
  98. return child_p;
  99. }
  100. //矩阵以及信息打印
  101. void emitPuzzleBoard( board_t *board ){
  102. int i;
  103. for (i = 0 ; i < MAX_BOARD ; i++) {
  104. if ((i%3) == 0)
  105. printf("\n");
  106. if (board->array[i] == 0)
  107. printf(" ");
  108. else
  109. printf("%c", 'A'+board->array[i]-1);
  110. }
  111. printf("\n");
  112. printf("f = %d, g = %d, h = %d\n", board->f, board->g, board->h);
  113. printf("\n");
  114. }
  115. //展示结果
  116. void showSolution(board_t *goal) {
  117. board_t *revList[MAX_LIST_ELEMENTS];
  118. int i = 0, j;
  119. printf("Solution:\n");
  120. //倒着存
  121. while(goal) {
  122. revList[i++] = goal;
  123. goal = goal->pred;
  124. }
  125. //正着输
  126. for (j = i-1; j >= 0; j--) {
  127. emitPuzzleBoard(revList[j]);
  128. printf("\n");
  129. }
  130. }
  131. //A*算法
  132. void astar() {
  133. board_t *cur_board_p, *child_p, *temp;
  134. int i;
  135. while(listCount(&openList_p)) {
  136. //在open表中选取f最小的节点,并做销毁处理
  137. cur_board_p = getListBest(&openList_p);
  138. //将已经运算过的节点加入colse表
  139. putList(&closedList_p, cur_board_p);
  140. //
  141. if(cur_board_p->h == 0) {
  142. //展示形成过程
  143. showSolution(cur_board_p);
  144. return;
  145. } else {
  146. //扩展
  147. //到达一定的深度找不到结果就停止扩展
  148. if(cur_board_p->depth > MAX_DEPTH) continue;
  149. //0能走四个方向
  150. for(i = 0; i < 4; i++) {
  151. child_p = getChildBoard(cur_board_p, i);
  152. if(child_p != (board_t *)0) {
  153. if(onList(&closedList_p, child_p->array, NULL)) {
  154. nodeFree(child_p);
  155. continue;
  156. }
  157. //若不在close表中,则先处理该节点
  158. //深度+1
  159. child_p->depth = cur_board_p->depth + 1;
  160. //计算h
  161. child_p->h = evaluateBoard(child_p);
  162. //计算g
  163. child_p->g = child_p->depth;
  164. //计算f = g + h
  165. child_p->f = (child_p->g * ALPHA) + (child_p->h * BETA);
  166. //判断该新节点是否存在于open表
  167. if(onList(&openList_p, child_p->array, NULL)) {
  168. //若存在则取出该节点
  169. temp = getList(&openList_p, child_p->array);
  170. //比较
  171. if(temp->g < child_p->g) {
  172. nodeFree(child_p);
  173. //新的节点g值大,将原本open表的节点放回
  174. putList(&openList_p, temp);
  175. } else {
  176. child_p->pred = cur_board_p;
  177. putList(&openList_p, child_p);
  178. }
  179. nodeFree(temp);
  180. } else {
  181. //若不存在,直接加入open表,当前节点为扩展节点的父节点
  182. child_p->pred = cur_board_p;
  183. putList(&openList_p, child_p);
  184. }
  185. }
  186. }
  187. }
  188. }
  189. }

资源释放

  1. //清空表
  2. void cleanupList(list_t *list_p) {
  3. int i;
  4. for (i = 0; i < MAX_LIST_ELEMENTS; i++) {
  5. if (list_p->elements[i] != (board_t *)0) {
  6. nodeFree(list_p->elements[i]);
  7. list_p->numElements--;
  8. }
  9. }
  10. return;
  11. }

主函数

  1. int main() {
  2. //初始节点状态,声明指向 board_t结构体类型的指针
  3. board_t *initial_p;
  4. //设置随机种子,用作随机数的生成
  5. RANDINIT();
  6. //初始化open表
  7. initList(&openList_p);
  8. //初始化close表
  9. initList(&closedList_p);
  10. //随机生成节点
  11. initial_p = initPuzzle();
  12. //将节点放入open表
  13. putList(&openList_p, initial_p);
  14. //astar
  15. astar();
  16. //清空open表
  17. cleanupList(&openList_p);
  18. //清空close表
  19. cleanupList(&closedList_p);
  20. return 0;
  21. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/786742
推荐阅读
相关标签
  

闽ICP备14008679号