习题 --- BFS

八数码

习题 --- BFS_第1张图片

 习题 --- BFS_第2张图片

如果想求最短路或者最小操作步数这样的问题,深搜是做不到的,需要用宽搜来做,这就是 DFS 和 BFS 的区别

DFS 第一次搜到的节点不一定是沿着最短路搜过去的,BFS 第一次搜到的节点一定是沿着最短路搜过去的

习题 --- BFS_第3张图片

给定一个 3 × 3 的网格,里面有 1 到 8 这八个数字和一个 ' x ',这个 ' x ' 其实是一个空格,里面没有数字,每次可以进行的操作是把一个现有的数字移到空格里面去,例如下面这张图,空格的位置如图所示,我们可以做的操作就是把空格周围的四个格子里面的数移到空格里面去,移动一次就算一次操作,第一种情况就是把 1 移到 x 这个空格里面去,第二种情况是把 4 移到 x 这个空格里面去,第三种情况是把 7 移到 x 这个空格里面去,对于这个状态有三种移动方式

题目给定我们一个初始的状态,让我们通过若干次类似刚才一样的操作,把初始状态变成给定的结果状态,问最少需要进行多少次变换才可以达到这样一个要求,如果不能达到结果状态的话就输出 -1

例如题目给出的初始状态经过三次变换就可以得到最终的结果状态,第一步把 4 和 x 交换得到第一个状态,第二步把 5 和 x 交换得到第二个状态,第三步把 8 和 x 交换得到最终状态

习题 --- BFS_第4张图片

题目要求最小的步数,因此可以用 BFS 来做,我们可以抽象一下,把所有的状态看成图当中的一个点,如果某一个状态通过一次变换之后可以变成另外一个状态(把状态 a 变换一下变成状态 b)的话,就连接一条从 a 指向 b 的边,边的权值是 1,问题就会转换成给定我们一个起点,问:从起点走到终点最少需要走多少步?所有边的权重都是 1,因此可以用 BFS 来求最短路,如果从起点走不到终点的话就返回 -1

习题 --- BFS_第5张图片

本题难点就是状态表示比较复杂,一般来说一个状态或者一个节点用一个数字来表示就可以了,比方说节点 1、节点 2、节点 3 . . . 这里的每一个状态是一个 3 × 3 的小矩阵,不是很方便地表示出来,在用宽搜求最短路的时候,需要定义两个数据结构,第一个是队列,如何把状态存储到队列里面,这是第一个问题;如何记录每一个状态的距离【dist 数组的实现距离,距离数组的下标如何表示?】,这是第二个问题,难点就是把以上几个问题处理好

习题 --- BFS_第6张图片

本题有很多种存储方式,一种比较简单的方式是直接用字符串来表示所有的状态,把整个 3 × 3 的数组展成一行,然后用一个字符串来表示,队列里面定义的时候,直接定义成 queue<string>就可以了,dist 可以用 c++ 里面的哈希表,或者 python 中的字典,或者 java 里面的字典存储

习题 --- BFS_第7张图片

第二个问题是如何来做状态转移,如何来看一下这个状态可以走到哪些其他状态,这个也是比较复杂的,例如给定我们一个字符串 "1234×5678",如何判断这个状态可以变成哪几个状态?

习题 --- BFS_第8张图片

需要分两步来做,第一步先把它恢复成 3 × 3 的样子,第二步就是转移,转移其实就是把 × 的上下左右四个位置分别枚举,把它的上下左右的数字分别移到 × 的位置上去,最后把移动之后的 3 × 3 的矩阵恢复成字符串

在字符串里面如果 x 的下标是 4 的话,把 x 转换成 3 × 3 的矩阵,它的横纵坐标是多少呢?可以用 int x = k / 3,y = k % 3;

#include 
//用哈希表存储所有距离
#include 
//宽搜需要用到的队列
#include 
#include 

using namespace std;

//BFS求最短距离
int bfs(string start)
{
    //定义end为终点
    string end = "12345678x";
    //宽搜需要用到的队列
    queue q;
    //定义一个距离数组
    unordered_map d;
    //先把start放到队列里面去
    q.push(start);
    //起点到起点的距离是0
    d[start] = 0;
    
    //上下左右四个位置
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

    //宽搜过程
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        //用一个变量存储t的距离
        int distance = d[t];

        //判断如果t是终点就可以提前结束了
        if(t == end) return distance;
        
        //状态转移 看一下当前这个状态可以变成哪些状态 在状态转移之前需要先找到x的位置 用k存储x的位置 find('x')返回x的下标
        int k = t.find('x');
        //[常用技巧] 可以把一个一维数组的下标转换成一个二维数组的下标 x、y是当前x的位置
        int x = k / 3, y = k % 3;
        //枚举把哪个数放过来 把x上下左右四个数移到x的位置上去
        for(int i = 0;i < 4;i++ )
        {
            //a、b表示x、y上下左右四个方向当中的某一个数字的坐标
            int a = x + dx[i], b = y + dy[i];
            //如果a和b没有出界
            if(a >= 0 && a < 3 && b >= 0 && b < 3)
            {
                //x、y是空格的位置 a、b是x、y上下左右当中的某一个数字 现在的目标是把a、b这个数字移到x、y上去 其实就是交换a、b和x、y
                swap(t[k], t[a * 3 + b]);
                //状态更新 x、y的下标是k 二维的a、b的下标对应到一维数组里面是什么呢? a * 3 + b
                
                //状态更新之后的情况
                if(!d.count(t))
                {
                    //如果当前更新完之后的t之前没有搜到过的话 就找到了一个新的状态 需要更新新的状态的距离
                    d[t] = distance + 1;
                    //把新的状态加到队列里面去
                    q.push(t);
                }
                //恢复状态
                swap(t[k], t[a * 3 + b]);
            }
        }
    }
    //如果找不到终点说明到不了终点 返回-1
    return -1;
}

int main()
{
    //start存储初始状态 首先读入9个字符
    for(int i = 0;i < 9;i++ )
    {
        char c;
        cin >> c;
        start += c;
    }
    //BFS
    cout << bfs(start) << endl;
    
    return 0;
}

你可能感兴趣的:(宽度优先,算法)