zoukankan      html  css  js  c++  java
  • 【算法解决实际问题】BFS解决益智游戏

    前言

    小时候家里大人不让玩手机,所以我常玩的是华容道益智游戏,一关就要解很久,解不开也没心思去做别的事了。如果用算法解决实际生活中的问题,那么我第一个想解决的是快速通关华容道。
    华容道是在一个拼图中有一个格子是空的,利用这个空着的格子去移动其他的滑块(卒、张飞、赵云、关羽等),最后顺利让曹操从中间的空格出来。以第二关七步成诗进行操作,最初,没有用考虑最少步数完成,如图:

    这种游戏一般都有一些套路,类似于魔方还原公式,使用的是一些技巧,我们不研究这个令人脑壳痛的技巧,学了算法后知道这个可以使用快乐无比的暴力搜索算法解决————BFS算法框架解决类似的益智游戏。

    BFS算法框架

    BFS核心思想是把一些问题抽象成图,从一个点开始,像四周扩散,找到到终点最近的距离。常使用的是队列这个数据结构,每次将一个节点周围的所有节点加入队列。

    BFS算法框架

    //计算从起点到终点最近距离
    int BFS(Node start,Node target){
    Queue<Node> q;//队列q,核心数据结构
    Set<Node> visited;//避免走回头路
    q.offer(start);//将起点加入队列
    visited.add(start);
    int step=0;//记录扩散的步数
    while(q not empty){
    	int sz=q.size();
    	/*将当前队列所有的节点像四周扩散*/
    	for(int i=0;i<sz;i++){
    		Node cur =q.poll();
    		/*划重点:这里判断是否到达终点*/
    		if(cur is target)
    			return step;
    		/*将cur的相邻界点加入队列*/
    		for(Node x:cur.adj()){
    			if(x not in visited){
    				q.offer(x);
    				visited.add(x);
    			}
    		}
    		/*划重点:更新步数*/
    		step++;
    	}
    }
    

    cur.adj()泛指cur相邻的节点,比如说二维数组中,cur上下左右四面的位置,就是相邻的节点。
    visited的主要作用是防止走回头路。

    BFS与DFS

    DFS用递归的形式,用到了栈结构,先进后出。
    BFS选取状态用队列的形式,先进先出。
    DFS的复杂度与BFS的复杂度大体一致,不同之处在于遍历的方式与对于问题的解决出发点不同。
    一般来说哈,在找最短路径的时候使用BFS,其他时候DFS偏多,主要是递归代码好写叭。

    双向BFS优化

    传统的BFS框架是从起点开始向四周扩散,遇到终点时停止。而双向BFS则是从起点和终点同时开始扩散,当两边有交集的时候停止。双向BFS还是遵循BFS算法框架的,只是不再使用队列,而是使用HashSet方便快捷判断两个集合是否有交集。while循环的最后交换q1和q2的内容,所以只要默认扩散q1就相对轮流扩散q1和q2。
    不过双向BFS也有局限性,必须得先知道终点在哪里。

    问题分析

    可以将其类比为滑动拼图问题,leetcode第773题滑动谜题就是滑动拼图问题,以此题为例,进行演算分析。
    题目描述如下:

    思路描述

    这是计算最小步数的问题,正如之前所说,遇到这类问题,最先思考BFS算法。而这个题目转化为BFS的时候,我们会面临这样的问题:

    1. 一般的BFS算法时从一个起点到终点进行寻路的,拼图问题是在不断的交换数字。
    2. 假设这个问题可以转化为BFS问题,那么起点与终点该如何处理?把数组放入队列是个麻烦低效的事情。
      BFS是一种暴力搜索算法
      解决第一个问题:只要涉及暴力穷举的问题,BFS就可以用,并且可以最快的找到最优解。可以将第一个问题转化为“怎么样穷举board当前局面下可能衍生出的所有局面?”可以以数字0为基准,将0和上下左右的数字进行交换就可以了:

      每次先找到数字0,然后和周围数字进行交换,形成新的局面加入队列,当第一次抵达终点时,就以最少步数赢得游戏。
      解决第二个问题:这是一个2x3的二维数组,可以压缩为一个一维的字符串。而二维数组有上下左右的概念,压缩为一维后,获得上下左右的索引成为了一个难点。可以手动写出来这个映射,如下:
    vector<vector<int>> neighbor={
      {1,3},
      {0,4,2},
      {1,5},
      {0,4},
      {3,1,5},
      {4,2}
    };
    

    这个含义是在一维字符串中,索引i在二维数组中的相邻索引为neighbor[i]:

    eg. negihbor[4]={3,1,5}

    这两个问题解决后,就可以套用之前讲到的BFS算法框架(套路):

    int slidingPuzzle(vector<vector<int>>& board) {
        int m = 2, n = 3;
        string start = "";
        string target = "123450";
        // 将 2x3 的数组转化成字符串
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                start.push_back(board[i][j] + '0');
            }
        }
        // 记录一维字符串的相邻索引
        vector<vector<int>> neighbor = {
            { 1, 3 },
            { 0, 4, 2 },
            { 1, 5 },
            { 0, 4 },
            { 3, 1, 5 },
            { 4, 2 }
        };
    
        /******* BFS 算法框架开始 *******/
        queue<string> q;
        unordered_set<string> visited;
        q.push(start);
        visited.insert(start);
        int step = 0;
        while (!q.empty()) {
            int sz = q.size();
            for (int i = 0; i < sz; i++) {
                string cur = q.front(); q.pop();
                // 判断是否达到目标局面
                if (target == cur) {
                    return step;
                }
                // 找到数字 0 的索引
                int idx = 0;
                for (; cur[idx] != '0'; idx++);
                // 将数字 0 和相邻的数字交换位置
                for (int adj : neighbor[idx]) {
                    string new_board = cur;
                    swap(new_board[adj], new_board[idx]);
                    // 防止走回头路
                    if (!visited.count(new_board)) {
                        q.push(new_board);
                        visited.insert(new_board);
                    }
                }
            }
            step++;
        }
        return -1;
        /******* BFS 算法框架结束 *******/
    }
    

    结果

    回到我们最初想要实现最少步数完成第二关,利用我们上面所讨论的BFS算法思想,在实际应用(微信小程序——经典三国华容道)上实践后得到七步完成第二关——七步成诗。如图:

    再以实际应用(应用市场————数字华容道快应用)进行测试,未使用算法和使用算法对比后,可见明显差别,如图所示:

    备注

    文章书写时结合了labuladong博主有关BFS的算法描述,在问题问分析和思路描述是看了许多博客资料,自己理解后的想法。实践结果可能存在一定误差,这是因为数字华容道的每次测试,开局所给的拼图是随机的,可能存在细微的差距。

    一往无前虎山行,拨开云雾见光明。
  • 相关阅读:
    lightoj1027_数学求期望
    lightoj1232_完全背包
    2013 ACM/ICPC Asia Regional Chengdu Online
    数位DP专题
    状态压缩DP专题
    树形DP专题
    hdu 1198 Farm Irrigation
    hdu 4739 Zhuge Liang's Mines 2013 ACM/ICPC Asia Regional Hangzhou Online
    hdu 4745 Two Rabbits 2013 ACM/ICPC Asia Regional Hangzhou Online
    动态规划专题uva
  • 原文地址:https://www.cnblogs.com/XIAOGUAI9/p/14666706.html
Copyright © 2011-2022 走看看