八数码问题

发布时间 2023-08-25 02:21:35作者: 失控D大白兔

一. 广度优先算法

使用队列记录当前层次的状态
同时使用哈希表防止重复遍历
单向广度优先是逐渐增大范围同时判断目标是否在范围内

int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};
int main()
{
    string board;
    cin>>board;//初始化棋盘
    string target = "123804765"; //初始化目标
    int depth = 0;//搜索深度
    queue<string> q;//队列记录当前状态
    unordered_set<string> s;//不重复遍历
    q.push(board);
    while(!q.empty()){
        int n = q.size();
        for(int i=0;i<n;i++){//遍历当前层次
            string &str = q.front(); q.pop(); //下面都是针对str状态来写
            if(str==target){//找到目标
                cout<<depth;
                return 0;
            }
            s.insert(str);//将已遍历状态记录下来,防止重复遍历
            int pos = str.find('0');
            int x = pos/3; int y = pos%3;
            for(int j=0;j<4;j++){//遍历四个方向,进行交换
                int nx = x + dir[j][0]; int ny = y + dir[j][1];
                int nextpos = nx*3+ny;
                if(nx<0||nx>2||ny<0||ny>2) continue;//下个位置不存在,或者是原来过来的位置,跳过
                swap(str[pos],str[nextpos]); //移动
                if(s.find(str)==s.end()) q.push(str);//加入一个新的状态
                swap(str[pos],str[nextpos]);//撤回
            }
        }
        depth++;
    }
    return 0;
}

二. 双向广度优先

双向广度优先交替增大范围,并且判断是否交叉


int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};

int main()
{
    string board;
    cin>>board;//初始化棋盘
    string target = "123804765"; //初始化目标
    int depth = 0;//搜索深度
    queue<string> q1;//队列记录当前状态
    unordered_set<string> s1;//不重复遍历
    queue<string> q2;//队列记录当前状态
    unordered_set<string> s2;//不重复遍历

    q1.push(board); 
    q2.push(target);
    while(!q1.empty()||!q2.empty()){
        int n = q1.size();
        for(int i=0;i<n;i++){//遍历当前层次
            string &str = q1.front(); q1.pop(); //下面都是针对str状态来写
            if(s2.count(str)){//找到目标
                cout<<depth-1;
                return 0;
            }
            s1.insert(str);//将已遍历状态记录下来,防止重复遍历
            int pos = str.find('0');
            int x = pos/3; int y = pos%3;
            for(int j=0;j<4;j++){//遍历四个方向,进行交换
                int nx = x + dir[j][0]; int ny = y + dir[j][1];
                int nextpos = nx*3+ny;
                if(nx<0||nx>2||ny<0||ny>2) continue;//下个位置不存在,或者是原来过来的位置,跳过
                swap(str[pos],str[nextpos]); //移动
                if(s1.find(str)==s1.end()) q1.push(str);//加入一个新的状态
                swap(str[pos],str[nextpos]);//撤回
            }
        }
        depth++;
        swap(q1,q2);
        swap(s1,s2);
    }
    return 0;
}

一. A*算法

广度优先+估值函数


二. IDA*算法

深度优先+估值函数

这里使用估值函数对深度优先搜索进行剪枝
估值函数为1~8每个数字的曼哈顿距离,这个同样也是理论上的最少步骤
同时提供另一个对不在位进行估值的函数
保证当前搜索的是最优解

string board;
string target;
int depth;//搜索深度

int h1(string &s){//估值函数1,根据曼哈顿距离
    int res = 0;
    for(int i=0;i<s.size();i++){
        if(s[i]=='0') continue;//不用计算0的匹配度
        int x1 = i/3; int y1 = i%3;
        int idx = target.find(s[i]);
        int x2 = idx/3; int y2 = idx%3;
        res += abs(x2-x1) + abs(y2-y1);
    }
    return res;
}

int h2(string &s){//估值函数2,根据不在位个数
    int res = 0;
    for(int i=0;i<s.size();i++){
        if(s[i]=='0') continue;
        if(s[i]!=target[i]) res++;
    }
    return res;
}

int dir[4][2] = {{1,0},{0,-1},{-1,0},{0,1}};

bool dfs(int d,int prepos){
    int cnt = h1(board);
    if(cnt==0) return 1;//边界条件,与目标一致
    if(d+cnt>depth) return 0;//直接剪枝,达到预期深度,不再递归

    int pos = board.find('0');
    int x = pos/3; int y = pos%3;
    for(int i=0;i<4;i++){//遍历四个方向,进行交换
        int nx = x + dir[i][0]; int ny = y + dir[i][1];
        int nextpos = nx*3+ny;
        if(nx<0||nx>2||ny<0||ny>2||nextpos==prepos) continue;//下个位置不存在,或者是原来过来的位置,跳过
        swap(board[pos],board[nextpos]);
        if(dfs(d+1,pos)) return true;
        swap(board[pos],board[nextpos]);
    }
    return false;
}


int main()
{
    cin>>board;//初始化棋盘
    target = "123804765"; //初始化目标
    depth = h1(board);//一开始评估一下局面,初始化一个深度
    while(!dfs(0,-1)) depth++;//初始深度为0,没有上一个步
    cout<<depth;
    return 0;
}