• 广度搜索图、树优化以及通用模板


    一、问题

    图、树的遍历算法当中有广度优先搜索与深度优先搜索,本文讲述广度优先搜索的优化。如图所示,有一个需求,从start开始找出到end的所有路径,如果不加限制暴力广度优先搜索的话,每一个节点都需要去访问看是否可以到目标终点,找出目标路径需要遍历2^5 次,但是当图或者树的深度过深时,遍历节点的数量会以以2为底的指数形式增长。比如树的深度为20时需要搜索2^20=1048576次,这时候就算仍然能找出路径,但是对于计算机的资源消耗可能会非常大,计算时间也会非常慢,对用户是非常不友好的。
    在这里插入图片描述

    二、情景故事

    想象一下:你第一次去你老丈人家里做客,你到了小区门口只知道你老丈人家住在2401室,你也不知道在哪栋楼里面,这个时候手机还没电了,所以你先到各个楼下看看女朋友有没有下来接你,结果没有,然后又坐电梯上去一个一个去找,上去发现不是老丈人家的2401又下来,吭哧吭哧把小区所有的2401室都找完了,终于找到了去老丈人家的路。

    场景2:你还是第一次去老丈人家做客,你还是只知道老丈人家住在2401室,手机没电了,你还是一个楼一个楼坐电梯找。但是你有一个关心你的女朋友发现你快到了,她肯定知道去楼下的路,就出门,坐电梯,下楼去找你,到了楼下,你刚好找了一个楼下楼碰到了,这时候你肯定知道她住在哪栋楼,怎么坐电梯上楼,你就跟着她上楼去了,其他的楼都不用再找了。(双向奔赴的爱情真美好)

    三、优化思路

    观察上图结构,发现有很多节点根本是到不了目标节点的,如果能把这些多余的消耗避免掉,复杂度能够极大的下降。
    在这里插入图片描述
    根据二中的场景故事,则是发现如果终点也能出来找找我们,我们会少走很多不必要的路的。
    回到算法当中就是维护两个队列,分别从起点向后找、终点向前找,每次从队列节点数小的一方开始搜索,当两个队列中的访问节点相遇时,一个节点肯定知道了对方来时的路,合在一起便是可通的路。

    新的搜索过程如下图所示:
    在这里插入图片描述
    这样做优化了非常多的节点,第一条产生出来的路径必然是最优路径。实际应用中也可以根据此方法进行扩展,我对我们系统中的路径搜索进行了优化之后速度还是非常快的。如果找的不止一条,有一些不合理路径可以根据最优深度进行过滤,比如单向深度大于最优深度的三分之二等等

    四、通用模板

    用java写的:

    /**
         * 搜索点对点主路经
         *
         * @param startCode      起点设备
         * @param targetCodes    终点设备
         * @param allPath        找出来的所有路径
         */
        public static void findMainPath(String startCode, String targetCode, Map<String, List<String>> allPath) {
            // 节点
            MapNode startNode = SearchMapServiceImpl.searchMap.get(startCode);
            MapNode targetNode = SearchMapServiceImpl.searchMap.get(targetCode);
            // 双向搜索队列
            Queue<String> startNodeQueue = new LinkedList<>();
            Queue<String> targetNodeQueue = new LinkedList<>();
    
            String startDeviceType = startNode.deviceAttr.get(Constants.SERVICE_TYPE).toString();
            String targetDeviceType = startNode.deviceAttr.get(Constants.SERVICE_TYPE).toString();
            // 把起点加进来
            startNodeQueue.add(startCode + Constants.ROUTE_SPLIT + startDeviceType);
            // 把目标加进来
            targetNodeQueue.add(targetCode + Constants.ROUTE_SPLIT + targetDeviceType);
    
            // 起点访问标志位
            Set<String> startFindFlagMap = new HashSet<>();
            // 终点访问标志位
            Set<String> targetFindFlagMap = new HashSet<>();
            startFindFlagMap.add(startCode);
            targetFindFlagMap .add(targetCode);
            while (!startNodeQueue.isEmpty()&& !targetNodeQueue.isEmpty()) {
                // 每次从队列个数最小的一端搜索
                if(startNodeQueue.size() <= targetNodeQueue.size()){
                    // 走的路
                    for (int i = 0; i < startNodeQueue.size(); i++) {
                        String footRoute = startNodeQueue.poll();
                        // 找出footRoute 下一个节点入队列,直到终点队列访问过下一个基点
                    }
                }else{
                    // 走的路
                    for (int i = 0; i < targetNodeQueue.size(); i++) {
                        String footRoute = targetNodeQueue.poll();
                        // 找出footRoute 下一个节点入队列,直到起点队列访问过下一个基点
                    }
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
  • 相关阅读:
    数组相关 内容 解析
    第3章 定义内存缓存和log4net日志中间件
    做自动化测试选择Python还是Java?
    Java开发者的Python快速进修指南:实战之跳表pro版本
    2.1.5 面向对象:类的方法(二)(Python)
    百度云原生数据库GaiaDB的HTAP与多地多活技术实践
    《QT实用小工具·四十一》无边框窗口
    c++11日期和时间工具-(std::chrono::system_clock)
    【JavaEE】Spring 事务(回顾mysql中的事务)
    【Vue-Element-Admin】导出el-table全部数据
  • 原文地址:https://blog.csdn.net/qq_39898191/article/details/126086566