BFS + 压缩状态

模板题:845. 八数码 - AcWing题库

 首先,对于这种步数为1,求最短步数的问题,我们应该立马想到BFS求最短路

难点在于状态表示

我们可以把每一个二维状态(3×3表格)压缩并看成一个一维字符串

并把所有的状态转换看成一个图

起点就是输入的状态,终点就是答案要求的状态,从一个状态到另一个状态可以看作连出去一条有向边,那么这就是一个求最短路问题了。

例如下图(初态为X321)(从左到右,从上到下),我们可以发现,图中有冗余(重复出现)的节点,在画图的时候,我们可以画出来,但在程序中,我们应该尽力避免。

一维和二维数组坐标之间的转换

这里的数组下表都从0开始

一维数组位置k到二维数组(n行n列)的映射 

x=k/n,y=k%n

二维数组(x,y)以为数组的映射

k=x*n+y

注意:我们二维数组规定为n行n列是有用意的,在二维->一维的转换中,行列的值必须相同!即便原来的数组只有两行三列,我们也要按三行三列去算!

下面的交换A和B的位置一题就是例子!

 

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
#include <queue>

using namespace std;

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

int bfs(string start)
{
    unordered_map<string, int> d;//状态到距离的映射的哈希表
    queue<string> q;
    string end = "12345678x";
    
    q.push(start);
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        
        int distance = d[t];
        //如果到了答案状态
        if(t == end)    return distance;
        
        //否则,找到空格的位置
        int k = t.find('x');
        int x = k / 3, y = k % 3;//一维数组转换成二维数组
        
        for(int i = 0; i < 4; i ++ )
        {
            int a = x + dx[i], b = y + dy[i];
            if(a < 0 || b < 0 || a >= 3 || b >= 3)  continue;
            
            //交换‘x’和与它相邻字符的位置
            swap(t[k], t[a * 3 + b]);//二维数组转换成一维数组
            if(!d.count(t))//这个状态没有走过
            {
                 d[t] = distance + 1;
                 q.push(t);
            }
            //加入队列之后还要换回来以供下一次使用
            swap(t[k], t[a * 3 + b]);//二维数组转换成一维数组
        }
        
    }
    return -1;//如果没找到符合的答案
}

int main()
{
    ios::sync_with_stdio(false);

    string start;
    for(int i = 0; i < 9; i ++ )
    {
        char ch;
        cin >> ch;
        start += ch;
    }
    
    int res = bfs(start);
    cout << res << endl;
    
    return 0;
}


类似题:3156. 卡片换位 - AcWing题库

#include <iostream>
#include <algorithm>
#include <cstring>
#include <unordered_map>
#include <queue>

using namespace std;

int aa, bb;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

int bfs(string start)
{
    queue<string> q;
    unordered_map<string, int> d;
    
    q.push(start);
    d[start] = 0;
    
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        
        int distance = d[t];
        int k1 = t.find('A'), k2 = t.find('B');
        if(k1 == bb && k2 == aa)    
        {
            // cout << "res: " << t << endl;
            return distance;
        }
        int k = t.find(' ');
        int x = k / 3, y = k % 3;//即便是两行三列,我们也要除3,而不是除2!
        
        for(int i = 0; i < 4; i ++ )
        {
            int xx = x + dx[i], yy = y + dy[i];
            if(xx < 0 || yy < 0 || xx >= 2 || yy >= 3)  continue;
            swap(t[k], t[xx * 3 + yy]);
            if(!d.count(t))
            {
                d[t] = distance + 1;
                q.push(t);
            }
            swap(t[k], t[xx * 3 + yy]);
        }
        
    }
    
    return -1;
}

int main()
{
    ios::sync_with_stdio(false);
    
    string start = "";
    for(int i = 0; i < 2; i ++ )
    {
        string s;
        getline(cin, s);
        start += s;
    }
    
    // cout << "s:" << start << endl;
    for(int i = 0; i < 6; i ++ )
    {
        if(start[i] == 'A') aa = i;
        else if(start[i] == 'B')  bb= i;
    }
    // cout << "a:b " << aa << ' ' << bb << endl;
    
    int res = bfs(start);
    
    cout << res << endl;
    
    
    return 0;
}

在这个代码中,看似正确,但其实我犯了一个很大的错误

题目中要求的是A和B的位置互换即可

我却想当然的直接把A和B的位置直接互换了

但其实这样其他位置放置的东西就确定了

虽然这是一个答案,但这不是最小值

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
#include <queue>

using namespace std;

int bfs(string start, string end)
{
    unordered_map<string, int> d;
    d[start] = 0;
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    queue<string> q;
    q.push(start);
    
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        
        int distance = d[t];
        if(t == end)    return distance;
        
        int k = t.find(' ');
        int x = k / 3, y = k % 3;
        for(int i = 0; i < 4; i ++ )
        {
            int a = x + dx[i], b = y + dy[i];
            if(a < 0 || b < 0 || a >= 2 || b >= 3)  continue;
            swap(t[k], t[a * 3 + b]);
            if(!d.count(t))
            {
                q.push(t);
                d[t] = distance + 1;
            }
            swap(t[k], t[a * 3 + b]);
        }
        
    }
    
    
    return -1;
}

int main()
{
    string start, end;
    int aa, bb;
    
    for(int i = 0; i < 2; i ++ )
    {
        string ts;
        getline(cin, ts);
        start += ts;
    }
    
    for(int i = 0; i < 6; i ++ )
    {
        if(start[i] == 'A') aa = i;
        else if(start[i] == 'B')    bb = i;
    }
    
    end = start;
    swap(end[aa], end[bb]);
    cout << start << "|||" << end << endl;
    
    int res = bfs(start, end);
    cout << res << endl;
    return 0;
}


变形题:179. 八数码 - AcWing题库

1


 变形体:4228. 八数码II - AcWing题库

暴力写法:TLE 

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <unordered_map>
#include <map>
#include <queue>

using namespace std;

string start, endstr;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
char way[4] = {'u', 'r', 'd', 'l'};

int bfs(string &pw)
{
    unordered_map<string, int> d;
    unordered_map<string, string> path;
    queue<string> q;
    q.push(start);
    d[start] = 0;
    
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        
        string pathway = path[t]; 
        int distance = d[t];
        if(t == endstr)    
        {
            // cout << path[t] << endl;
            pw = pathway;
            return distance;
        }
        int k = t.find('X');
        int x = k / 3, y = k % 3;
        for(int i = 0; i < 4; i ++ )
        {
            int a = x + dx[i], b = y + dy[i];
            if(a < 0 || b < 0 || a >= 3 || b >= 3)  continue;
            swap(t[k], t[a * 3 + b]);
            if(!d.count(t))
            {
                q.push(t);
                d[t] = distance + 1;
                path[t] = pathway + way[i];
            }
            swap(t[k], t[a * 3 + b]);
        }
        
    }
    return -1;
}

int main()
{
    ios::sync_with_stdio(false);
    
    int n;  cin >> n;
    for(int i = 1; i <= n; i ++ )
    {
        string path;
        cin >> start >> endstr;
        int res = bfs(path);
        cout << "Case " << i << ": " << res << endl;
        cout << path << endl;
    }
    return 0;
}

正解:IDA* 

#include<bits/stdc++.h>
using namespace std;
using pis = pair<int, string>;

int dx[] = {1, 0, 0, -1}, dy[] = {0, -1, 1, 0};
char d[] = {'d', 'l','r', 'u'};//按照字典序排列

char s[10], ed[10];
int dep, x, y;
int edx[9], edy[9], path[100];

bool dfs(int u, int ida){
    if(u + ida > dep) return 0;//剪枝1:现有的深度和估价出的深度要超过最大深度的话就直接return
    if(!strcmp(s, ed)){//如果 s == ed
        cout << u << '\n';
        for(int i = 0; i < u; i++) cout << d[path[i]];
        cout << '\n';
        return 1;
    }

    int tmpx = x, tmpy = y;
    for(int i = 0; i < 4; i++){
        if(u && path[u - 1] + i == 3) continue;//剪枝2:不走反方向
        int nx = tmpx + dx[i], ny = tmpy + dy[i];
        if(nx >= 0 && ny >= 0 && nx < 3 && ny < 3){
            int a = s[nx * 3 + ny] - '1', ret = 0;//a:X即将走的位置的数字,ret:ida的改变量
            ret += abs(tmpx - edx[a]) - abs(nx - edx[a]) + abs(tmpy - edy[a]) - abs(ny - edy[a]);//
            x = nx, y = ny, path[u] = i;//更新x,y,path

            swap(s[nx * 3 + ny], s[tmpx * 3 + tmpy]);//更新s
            if(dfs(u + 1, ida + ret)) return 1;
            swap(s[nx * 3 + ny], s[tmpx * 3 + tmpy]);//还原s
        }
    }
    x = tmpx, y = tmpy;//还原x,y,这个位置没写,找了2个小时
    return 0;
}
int main(){
    ios::sync_with_stdio(0); cin.tie(0);
    int tt; cin >> tt;
    for(int i = 1; i <= tt; i++){
        cout << "Case " << i << ": ";
        for(int i = 0; i < 9; i++) cin >> s[i];
        for(int i = 0; i < 9; i++) cin >> ed[i];

        for(int i = 0; i < 9; i++){
            if(ed[i] != 'X') edx[ed[i] - '1'] = i / 3, edy[ed[i] - '1'] = i % 3;//记录每一位的x,y值

            if(s[i] == 'X') x = i / 3, y = i % 3;//初始化位置x,y
        }
        int ida = 0;
        for(int i = 0; i < 9; i++)
            if(s[i] != 'X') ida += abs(i / 3 - edx[s[i] - '1']) + abs(i % 3 - edy[s[i] - '1']);
        for(dep = 0; ;dep++) if(dfs(0, ida)) break;
    }

    return 0;
}

posted @ 2022-05-05 08:41  光風霽月  阅读(21)  评论(0编辑  收藏  举报