• 八数码问题


    八数码问题

    我想大家小时候一定玩过八数码的游戏,如下图:在一个九宫格里面放入8个数字,数字只能上下左右移动,并且只能移动到空白处。通过若干此移动后,能把数字移动成图1.1右方所示图案。

     

    图1.1(左边为开始格局,右边为移动后最终格局

    下图是图1.1下一个格局的三种情况:

    请添加图片描述

    如果按正常的思维,采用盲目搜索的话,不仅搜索的次数多,而且往往容易陷入死循环中,所以面对此问题需要一种策略——启发式搜索

    启发式搜索:启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。这样可以省略大量无谓的搜索路径,提高了效率

    解决此问题的启发策略:

    每次移动的时候,正确位置数码的个数要大于交换前正确位置数码个数。

    正确位置数码个数:每个数码的位置与最终格局的对比,如果位置相同,则说明此数码在正确位置。

    如下图:

     

    图1.3

    图1.3中右边为最终格局,左边为当前格局,红色字体标识的数码为 正确位置数码,由此可以发现其正确位置的数码个数为4个。那么图1.2中正确数码如下图所示:

    请添加图片描述

     

    由上图所示可得,正确位置数码个数大于等于4的只有左下方的格局,那么下一步选择的就是左下方的格局,再次调用次算法如下图:请添加图片描述
    这样一步步的最终即可得到最终格局
     

    BFS+Cantor 解决方法
    1. #include
    2. const int LEN = 362880; //状态共9!=362880种
    3. using namespace std;
    4. struct node
    5. {
    6. int state[9]; //记录一个八数码的排列,即一个状态
    7. int dis; //记录到起点的距离
    8. };
    9. int dir[4][2] = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};
    10. //左,上,右,下顺时针方向,左上角的坐标是(0,0)
    11. int visited[LEN] = {0}; //与每个状态对应的记录,Cantor()函数对它置数,并判重
    12. int start[9]; //开始状态
    13. int goal[9]; //目标状态
    14. long int factory[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880}; // Cantor()用到的常数 即阶乘
    15. //用康托展开判重
    16. bool Cantor(int str[], int n)
    17. {
    18. long result = 0;
    19. for (int i = 0; i < n; i++)
    20. {
    21. int counted = 0;
    22. for (int j = i + 1; j < n; j++)
    23. {
    24. //当前未出现的元素排在第几个
    25. if (str[i] > str[j])
    26. ++counted;
    27. }
    28. result += counted * factory[n - i - 1];
    29. }
    30. if (!visited[result]) //没有被访问过
    31. {
    32. visited[result] = 1;
    33. return 1;
    34. }
    35. else
    36. return 0;
    37. }
    38. int bfs()
    39. {
    40. node head;
    41. memcpy(head.state, start, sizeof(head.state)); //复制起点的状态
    42. head.dis = 0;
    43. queue q; //队列中的内容是记录状态
    44. Cantor(head.state, 9); //用康托展开判重,目的是对起点的visited[]赋值
    45. q.push(head); //第一个进队列的是起点状态
    46. while (!q.empty()) //处理队列
    47. {
    48. head = q.front();
    49. q.pop(); //可在此处打印head.state,看弹出队列的情况
    50. int z;
    51. for (z = 0; z < 9; z++) //找到这个状态中元素0的位置
    52. {
    53. if (head.state[z] == 0) //找到了
    54. break;
    55. }
    56. int x = z % 3; //横坐标
    57. int y = z / 3; //纵坐标
    58. for (int i = 0; i < 4; i++) //上,下,左,右最多可能有4个新状态
    59. {
    60. int newx = x + dir[i][0]; //元素0转移后的新坐标
    61. int newy = y + dir[i][1];
    62. int nz = newx + 3 * newy; //转化为一维
    63. if (newx >= 0 && newx < 3 && newy >= 0 && newy < 3) //未越界
    64. {
    65. node newnode;
    66. memcpy(&newnode, &head, sizeof(struct node)); //复制这个新状态
    67. swap(newnode.state[z], newnode.state[nz]); //把0移动到新的位置
    68. newnode.dis++;
    69. if (memcmp(newnode.state, goal, sizeof(goal)) == 0) //与目标状态对比
    70. return newnode.dis; //到达目标状态,返回距离,结束
    71. if (Cantor(newnode.state, 9)) //用康托展开判重
    72. q.push(newnode); //把新的状态放入队列
    73. }
    74. }
    75. }
    76. return -1; //没找到
    77. }
    78. int main()
    79. {
    80. for (int i = 0; i < 9; i++) //初始状态
    81. cin >> start[i];
    82. for (int i = 0; i < 9; i++) //目标状态
    83. cin >> goal[i];
    84. int num = bfs();
    85. if (num != -1)
    86. cout << num << endl;
    87. else
    88. cout << "Impossible" << endl;
    89. return 0;
    90. }
    A* + Set 解决方法
    1. #include
    2. using namespace std;
    3. const int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
    4. int fx, fy;
    5. char ch;
    6. struct Matrix
    7. {
    8. int e[5][5];
    9. bool operator<(Matrix x) const
    10. {
    11. for (int i = 1; i <= 3; i++)
    12. for (int j = 1; j <= 3; j++)
    13. if (e[i][j] != x.e[i][j])
    14. return e[i][j] < x.e[i][j];
    15. return false;
    16. }
    17. } first, standard;
    18. // 函数h可以定义为,不在应该在的位置的数字个数。
    19. // 容易发现h满足以上两个性质,此题可以使用 A*算法求解。
    20. int h(Matrix m)
    21. {
    22. int ret = 0;
    23. for (int i = 1; i <= 3; i++)
    24. for (int j = 1; j <= 3; j++)
    25. if (m.e[i][j] != standard.e[i][j])
    26. ret++;
    27. return ret;
    28. }
    29. struct node
    30. {
    31. Matrix matrix;
    32. int t;
    33. bool operator<(node x) const { return t + h(matrix) > x.t + h(x.matrix); }
    34. } x;
    35. priority_queue qu; //搜索队列
    36. set st; //防止搜索队列重复
    37. int main()
    38. {
    39. standard.e[1][1] = 1; //定义标准表
    40. standard.e[1][2] = 2;
    41. standard.e[1][3] = 3;
    42. standard.e[2][1] = 8;
    43. standard.e[2][2] = 0;
    44. standard.e[2][3] = 4;
    45. standard.e[3][1] = 7;
    46. standard.e[3][2] = 6;
    47. standard.e[3][3] = 5;
    48. for (int i = 1; i <= 3; i++) //输入
    49. for (int j = 1; j <= 3; j++)
    50. {
    51. scanf(" %c", &ch);
    52. first.e[i][j] = ch - '0';
    53. }
    54. qu.push({first, 0});
    55. while (!qu.empty())
    56. {
    57. x = qu.top();
    58. qu.pop();
    59. if (!h(x.matrix))
    60. { //判断是否与标准矩阵一致
    61. printf("%d\n", x.t);
    62. return 0;
    63. }
    64. for (int i = 1; i <= 3; i++)
    65. for (int j = 1; j <= 3; j++)
    66. if (!x.matrix.e[i][j])
    67. fx = i, fy = j; //如果该点上的数不为0
    68. for (int i = 0; i < 4; i++)
    69. { //对四种移动方式分别进行搜索
    70. int xx = fx + dx[i], yy = fy + dy[i];
    71. if (1 <= xx && xx <= 3 && 1 <= yy && yy <= 3)
    72. {
    73. swap(x.matrix.e[fx][fy], x.matrix.e[xx][yy]);
    74. if (!st.count(x.matrix))
    75. st.insert(x.matrix),
    76. qu.push({x.matrix, x.t + 1}); //这样移动后,将新的情况放入搜索队列中
    77. swap(x.matrix.e[fx][fy], x.matrix.e[xx][yy]); //如果不这样移动的情况
    78. }
    79. }
    80. }
    81. return 0;
    82. }

  • 相关阅读:
    (11)点云数据处理学习——Colored point cloud registration(彩色点注册)
    密码学技术总结
    [杂记]C++中关于虚函数的一些理解
    Vue笔记之入门
    采用策略分布曲线评估信用风险模型的效果
    2023计算机毕业设计题目 毕设选题大全
    【jQuery Demo】图片瀑布流实现
    分布式 PostgreSQL 集群(Citus)官方示例 - 多租户应用程序实战
    springboot集成Redis
    javacc之路5---词法分析器技巧
  • 原文地址:https://blog.csdn.net/m0_61983575/article/details/127823502