广度优先搜索解决欧拉回路时间复杂度_浅析迷宫搜索类的双向bfs问题(附例题解析)...

4549f678d030f5d024fe34c9bfadcf9c.gif

前言

在搜索问题中,以迷宫问题最具有代表性,无论是八皇后的回溯问题,还是dfs找出口,bfs找最短次数等等题目的问题。在我们刚开始ac的时候、可能有着很多满足感!感觉是个迷宫问题咱么都可以给他这么搜出来 !!

0bc474817ccb0abc9ed287063196db45.png

然而,当数据达到一定程度,我们使用简单的方法肯定会爆炸的,各种TLE(超时),不分析原因还会一直提交一直TLE。就可能需要一些特殊的巧妙方法处理,比如各种剪枝优先队列A*dfs套bfs,又或者利用一些非常厉害的数学方法比如康托展开(逆展开)等等。而今天,我们谈谈双向bfs。(通常可以将时间复杂度优化为原时间的根号级别)

93c607da49842b4afc3e41c979d49abb.png

bfs类问题

bfs又称广度优先搜索

  • 估计大部分人第一次接触bfs的时候是在学习数据结构的二叉树的层序遍历!借助一个队列一层一层遍历。

  • 第二次估计就是在学习图论的时候,给你一个图,让你写出一个bfs遍历的顺序。

此后再无bfs…

而很多笔试面试还是其他机试其实对bfs的要求远远不止那么低的,需要能够处理一些小问题、写出对应代码。而且bfs可以处理很多问题,很多dfs搜索能够解决的问题bfs也能解决很多(相反也成立),并且很多跟状态有些关系的用bfs更好控制,因为bfs借助的是一个队列实现,队列中储存节点就可以保存一些节点的状态。

82e5d1f16627085a2ebba4aa017594be.png

不过bfs并不是万能的,具体问题要看迷宫的大小的,迷宫长宽没增加一个数,那么这个数量级增加是非常大的,因为搜索次数大概和边长的指数级别有关系。当然这里不详细介绍bfs了,大家可以看以前的一篇文章。数据结构与算法—深度、宽度优先(dfs,bfs)搜索

双向bfs

什么样的情况可以使用双向bfs来优化呢?其实双向bfs的主要思想是问题的拆分吧,比如在一个迷宫中可以往下往右行走,问你有多少种方式从左上到右下。

  • 正常情况下,我们就是搜索遍历,如果迷宫边长为n,那么这个复杂度大概是2^n^级别.

  • 但是实际上我们可以将迷宫拆分一下,比如根据对角线(比较多),将迷宫一分为二。其实你的结果肯定必然经过对角线的这些点对吧!我们只要分别计算出各个对角线各个点的次数然后相加就可以了!

  • 怎么算? 就是从(0,0)到中间这个点mid的总次数为n1,然后这个mid到(n,n)点的总次数为n2,然后根据排列组合总次数就是n1*n2(n1和n2正常差不多大)这样就可以通过乘法减少加法的运算次数啦!

  • 简单的说,从数据次数来看如果直接搜索全图经过下图的那个点的次数为 n1 * n2 次,如果分成两个部分相乘那就是n1+n2次。两者差距如果n1,n2=1000左右,那么这么一次差距是平方(根号)级别的。从搜索图形来看其实这么一次搜索是本来一个n*n大小的搜索转变成n次(每次大概是(n/2) * (n/2)大小的迷宫搜索两次)。也就是如果18*18的迷宫如果使用直接搜索,那么大概2^18次方量级,而如果采用双向bfs,那么就是2^9这个量级。

9d8d11077180b5687b74f65d3b475b7b.png

例题实战

题目链接:http://oj.hzjingma.com/contest/problem?id=20&pid=8#problem-anchor

089025121f3954772e356e926b9a67d1.png
30576a047192db7e5e51cb876e0f7a90.png

分析:对于题目的要求还是很容易理解的,就是找到所有的路径种类,再判断其中是对称路径的有几个输出即可!

对于一个普通思考是这样的,首先是进行dfs,然后动态维护一个字符串,每次跑到最后判断这个路径字符串是否满足对称要求,如果满足那么就添加到容器中进行判断。可惜很遗憾这样是超时的,仅能通过40%的样例。

接着用普通bfs进行尝试,维护一个node节点,每次走的时候路径储存起来其实这个效率跟dfs差不多依然超时。只能通过40%数据。

接下来就开始双向bfs进行分析

  • 既然只能右下,那么对角线的那个位置的肯定是中间的那个字符串的!它的存在不影响是否对称的(n*n的迷宫路径长度为n-1 + n为奇数).

  • 我们判断路径是否对称,只需要判断从(1,1)到对角节点k(设为k节点)的路径有没有和(n,n)到k相同的。如果有路径相同的那么就说明这一对构成对称路径

  • 在具体实现上,我们对每个对角线节点可以进行两次bfs(一次左上到(1,1),一次右下到(n,n)).并且将路径放到两个hashset(set1,set2)中,跑完之后用遍历其中一个hashset中的路径,看看另一个set是否存在该路径,如果存在就说明这个是对称路径放到 总的hashset(set) 中。对角线每个位置都这样判断完最后只需要输出总的hashset(set)的集合大小即可!

ac代码如下:

import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.Queue;
import java.util.Scanner;
import java.util.Set;

public class test2 {    
    static class node{
         int x;
         int y;
        String path="";
        public node() {}
        public node(int x,int y,String team){
            this.x=x;
            this.y=y;
            this.path=team;
        }
    }
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        Setset=new HashSet();//储存最终结果int n=Integer.parseInt(sc.nextLine());char map[][]=new char[n][n];for(int i=0;i        {
            String string=sc.nextLine();
            map[i]=string.toCharArray();
        }
        Queueq1=new ArrayDeque();//左上的队列
        Queueq2=new ArrayDeque();//右下的队列for(int i=0;i        {
            q1.clear();q2.clear();
            Setset1=new HashSet();//储存zuoshang
            Setset2=new HashSet();//储右下
            q1.add(new node(i,n-1-i,""+map[i][n-1-i]));
            q2.add(new node(i,n-1-i,""+map[i][n-1-i]));while(!q1.isEmpty()&&!q2.isEmpty())
            {
                node team=q1.poll();
                node team2=q2.poll();if(team.x==n-1&&team.y==n-1)//到终点,将路径储存
                {//System.out.println(team2.path);   
                    set1.add(team.path);
                    set2.add(team2.path);
                }else {if(team.x1)//可以向下
                    {
                        q1.add(new node(team.x+1, team.y, team.path+map[team.x+1][team.y]));
                    }if(team.y1)//可以向右
                    {
                        q1.add(new node(team.x, team.y+1, team.path+map[team.x][team.y+1]));
                    }if(team2.x>0)//上
                    {
                        q2.add(new node(team2.x-1, team2.y, team2.path+map[team2.x-1][team2.y]));
                    }if(team2.y>0)//左
                    {
                        q2.add(new node(team2.x, team2.y-1, team2.path+map[team2.x][team2.y-1]));
                    }
                }
            }for(String va:set1)
            {if(set2.contains(va))
                {
                    set.add(va);
                }
            }
        }
        System.out.println(set.size());     
    }
}
71bce7d95fd3c1f5295ab0546fbf752a.png
0839e649c3bc7d4b16bc3ce0517bb1c4.gif
推荐阅读: 从阶乘、斐波那契、汉诺塔剖析彻底搞懂递归算法 数据结构能干吗,我花了一夜给女朋友写个走迷宫游戏 最短路径—弄懂Dijkstra(迪杰斯特拉)算法 图解+手撕冒泡排序、快速排序 再不怕和老外聊天了! 我用python写了个微信聊天翻译助手! 剑指offer(47-67题)终极篇

32f55db9f868008605252fe64f85306b.png

长按识别

关注我们

7144ad13abf9b780b2b244345b12322b.png转载是一种动力 分享是一种美德

7bd5e76578e6f289998a37cd65c31c54.png

4babf2f947fb7cc4b156f4cc5b06a439.gif
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值