搜索—八数码问题与康托判重

八数码难题

题目描述

\(3\times 3\) 的棋盘上,摆有八个棋子,每个棋子上标有 \(1\)\(8\) 的某一数字。棋盘中留有一个空格,空格用 \(0\) 来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为 \(123804765\)),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

输入格式

输入初始状态,一行九个数字,空格用 \(0\) 表示。

输出格式

只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数。保证测试数据中无特殊无法到达目标状态数据。

样例 #1

样例输入 #1

283104765

样例输出 #1

4

提示

样例解释

图中标有 \(0\) 的是空格。绿色格子是空格所在位置,橙色格子是下一步可以移动到空格的位置。如图所示,用四步可以达到目标状态。

并且可以证明,不存在更优的策略。

分析

把一个棋局看成一个状态图,总共有9!=362880个状态。从初始棋局开始.每次移动转到下一个状态,到达目标棋局后停止。
八数码问题是一个经典的BFS问题。八数码从初始状态出发.每次转移都逐步逼近目标状态。每转移一次.步数加一,当到达目标时,经过的步数就是最短路径
下图是样例的转移过程。该图中起点为(A.0).A表示状态,即{123084765}这
个棋局:0是距离起点的步数。从初始状态A出发,移动数字0到邻居位置,按左、上、右、下的顺时针顺序,有3个转移状态B 、C、 D:目标状态是F.停止
image
用队列描述这个BFS过程:
(1)A进队,当前队列是{A};
(2) A出队.A的邻居B.C.D进队,当前队列是{B.C.D}.步数为1:
(3)B出队,E进队,当前队列是{C.D.E}.E的步数为2;
(4) C出队.转移到F.检验F是目标状态,停止,输出F的步数2。
仔细分析上述过程.发现从B状态出发实际上有Y、X两个转移方向.而X正好是初始状态A,重复了。同理Y状态也是重复的。如果不去掉这些重复的状态,程序会产生很多无效操作,复杂度会很大,所以关键是判重。使用暴力法最大对比9!*9!,不行。

借助数学方法——康托展开可以快速判重



第1行是0~8这9个数字的全排列,共9!-362880个,按从小到大排序。第2行是每个排列对应的位置,例如最小的{012345678}在第0个位置,最大的(876543210}在最后的362880-1这个位置。
函数Cantor()实现的功能是:输人一个排列.即第1行的某个排列.计算出它的Cantor值.即第2行对应的数
Cantor()的复杂度为O(n2),n是集合中元素的个数。在本题中.完成搜索和判重的总复杂度是O(n!2).远比用暴力判重的总复杂度O(n!n!)小.有了这个函数.八数码的程序能很快判重:每转移到一个新状态,就用Cantor()判断这个状态是否被处理过,如果处理过,则不转移。
下图是判断2143是{1,2,3,4}的全排列中第几大的数

上述过程的反过程是康托逆展开:某个集合的全排列.输人一个数字k.返回第k大的排列

代码实现

#include <bits/stdc++.h>
using namespace std;
const int LEN = 362880;
struct node {
   
   int state[9];
   int dis; 
};
int dir[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
int visited [LEN] = {0};
int start[9] = {1 ,2 ,3 ,8 ,0 ,4 ,7 ,6 ,5};
int goal[9];
long int factory[] = {1,1,2,6,24,120,720,5040,40320,362880};
bool Cantor(int str[],int n){
    long result = 0;
    for(int i = 0;i < n;i++){
        int counted = 0;
        for(int j = i+1;j < n;j++){
            if(str[i]>str[j])
                ++counted;
            }
            result += counted * factory[n-i-1];
    }
    if(!visited[result]){
        visited[result] = 1;
        return 1;
    }
    else
        return 0;
}
int bfs(){
    node head;
    memcpy(head.state,start,sizeof(head.state));
    head.dis = 0;
    queue<node>q;
    Cantor(head.state,9);
    q.push(head);
    while(!q.empty()){
        head = q.front();
        q.pop();
        int z;
        for(z = 0;z<9;z++)
            if(head.state[z]==0)
                break;
    int x = z%3;
    int y = z/3;
    for(int i = 0;i<4;i++){
        int newx = x+dir[i][0];
        int newy = y+dir[i][1];
        int nz = newx + newy*3;
        if(newx>=0&&newx<3&&newy>=0&&newy<3){
           node newnode;
           memcpy(&newnode,&head,sizeof(struct node));
           swap(newnode.state[z],newnode.state[nz]);
           newnode.dis++;
           if(memcmp(newnode.state,goal,sizeof(goal))==0) 
                return newnode.dis;
            if(Cantor(newnode.state,9))
                q.push(newnode);
            }
        }
   }
   return -1;
}
int main()
{
    string s;cin>>s;
    for(int i = 0;i < 9;i++){
        goal[i] = s[i] - '0';
    }
    if(memcmp(&start,goal,sizeof(goal))==0)cout<<0<<endl;
    else{
            int num = bfs();
            cout<<num<<endl;
        }
    return 0;
}
posted @ 2023-08-21 02:19  LongDz  阅读(58)  评论(0)    收藏  举报