双向最佳路径优先搜索算法

news2024/11/26 5:32:13

概念

双向最佳优先搜索(Bidirectional Best-First Search)是一种图搜索算法,用于在给定的图或树中找到两个节点之间的最短路径。该算法尝试从起始节点和目标节点同时扩展搜索,直到两个搜索方向相遇。

双向最佳优先搜索的步骤如下:

1、初始化起始节点和目标节点,并分别创建两个优先队列(open1和open2)保存待扩展的节点。

2、设置两个哈希表(closed1和closed2)用于存放已扩展的节点,并初始化为空。

3、将起始节点和目标节点分别放入open1和open2中,并计算起始节点和目标节点的启发式函数值(例如,启发式函数可以使用估计的最短路径长度)。

4、进入循环,直到open1或open2为空:
    4.1、从open1中取出启发式函数值最小的节点,并将其加入closed1中。
    4.2、对该节点进行扩展,生成其所有邻居节点,并计算它们的启发式函数值。
    4.3、如果某个邻居节点已在closed2中出现,意味着两个搜索方向相遇,找到了最短路径。
    4.4、否则,将未出现在closed1中的邻居节点加入open1。
    4.5、从open2中以相同的方式处理节点,并将其加入closed2中。

5、如果搜索结束时没有找到最短路径,表示起始节点和目标节点之间没有连接。

双向最佳优先搜索相较于传统的单向搜索算法,可以大幅减少搜索范围,提高搜索效率。由于每次扩展都是在两个方向上进行,搜索空间的大小可以被双向同时扩展的方式削减一半。

如下图,左侧是直接进行次搜索产生的搜索树,右侧是双向搜索的两棵搜索树,避免了层数过深时分支数量的大规模增长。
在这里插入图片描述

代码示例:

import os
import sys
import math
import heapq
import matplotlib.pyplot as plt


class BidirectionalBestFirst:
    def __init__(self, s_start, s_goal, heuristic_type,xI, xG):
        self.s_start = s_start
        self.s_goal = s_goal
        self.heuristic_type = heuristic_type
        self.motions = [(-1, 0), (-1, 1), (0, 1), (1, 1),
                        (1, 0), (1, -1), (0, -1), (-1, -1)]
        self.u_set = self.motions  # feasible input set
        self.obs = self.obs_map()  # position of obstacles

        self.OPEN_fore = []  # OPEN set for forward searching
        self.OPEN_back = []  # OPEN set for backward searching
        self.CLOSED_fore = []  # CLOSED set for forward
        self.CLOSED_back = []  # CLOSED set for backward
        self.PARENT_fore = dict()  # recorded parent for forward
        self.PARENT_back = dict()  # recorded parent for backward
        self.g_fore = dict()  # cost to come for forward
        self.g_back = dict()  # cost to come for backward
        self.x_range = 51  # size of background
        self.y_range = 31
        
        
        self.xI, self.xG = xI, xG
        self.obs = self.obs_map()
    def init(self):
        """
        initialize parameters
        """

        self.g_fore[self.s_start] = 0.0
        self.g_fore[self.s_goal] = math.inf
        self.g_back[self.s_goal] = 0.0
        self.g_back[self.s_start] = math.inf
        self.PARENT_fore[self.s_start] = self.s_start
        self.PARENT_back[self.s_goal] = self.s_goal
        heapq.heappush(self.OPEN_fore,
                       (self.f_value_fore(self.s_start), self.s_start))
        heapq.heappush(self.OPEN_back,
                       (self.f_value_back(self.s_goal), self.s_goal))

    def obs_map(self):
        """
        Initialize obstacles' positions
        :return: map of obstacles
        """

        x = 51
        y = 31
        obs = set()

        for i in range(x):
            obs.add((i, 0))
        for i in range(x):
            obs.add((i, y - 1))

        for i in range(y):
            obs.add((0, i))
        for i in range(y):
            obs.add((x - 1, i))

        for i in range(10, 21):
            obs.add((i, 15))
        for i in range(15):
            obs.add((20, i))

        for i in range(15, 30):
            obs.add((30, i))
        for i in range(16):
            obs.add((40, i))

        return obs

    def animation(self, path, visited, name):
        self.plot_grid(name)
        self.plot_visited(visited)
        self.plot_path(path)
        plt.show()

    def animation_bi_astar(self, path, v_fore, v_back, name):
        self.plot_grid(name)
        self.plot_visited_bi(v_fore, v_back)
        self.plot_path(path)
        plt.show()

    def plot_grid(self, name):
        obs_x = [x[0] for x in self.obs]
        obs_y = [x[1] for x in self.obs]

        plt.plot(self.xI[0], self.xI[1], "bs")
        plt.plot(self.xG[0], self.xG[1], "gs")
        plt.plot(obs_x, obs_y, "sk")
        plt.title(name)
        plt.axis("equal")

    def plot_visited(self, visited, cl='gray'):
        if self.xI in visited:
            visited.remove(self.xI)

        if self.xG in visited:
            visited.remove(self.xG)

        count = 0

        for x in visited:
            count += 1
            plt.plot(x[0], x[1], color=cl, marker='o')
            plt.gcf().canvas.mpl_connect('key_release_event',
                                         lambda event: [exit(0) if event.key == 'escape' else None])

            if count < len(visited) / 3:
                length = 20
            elif count < len(visited) * 2 / 3:
                length = 30
            else:
                length = 40
            #
            # length = 15

            if count % length == 0:
                plt.pause(0.001)
        plt.pause(0.01)

    def plot_path(self, path, cl='r', flag=False):
        path_x = [path[i][0] for i in range(len(path))]
        path_y = [path[i][1] for i in range(len(path))]

        if not flag:
            plt.plot(path_x, path_y, linewidth='3', color='r')
        else:
            plt.plot(path_x, path_y, linewidth='3', color=cl)

        plt.plot(self.xI[0], self.xI[1], "bs")
        plt.plot(self.xG[0], self.xG[1], "gs")

        plt.pause(0.01)

    def plot_visited_bi(self, v_fore, v_back):
        if self.xI in v_fore:
            v_fore.remove(self.xI)

        if self.xG in v_back:
            v_back.remove(self.xG)

        len_fore, len_back = len(v_fore), len(v_back)

        for k in range(max(len_fore, len_back)):
            if k < len_fore:
                plt.plot(v_fore[k][0], v_fore[k][1], linewidth='3', color='gray', marker='o')
            if k < len_back:
                plt.plot(v_back[k][0], v_back[k][1], linewidth='3', color='cornflowerblue', marker='o')

            plt.gcf().canvas.mpl_connect('key_release_event',
                                         lambda event: [exit(0) if event.key == 'escape' else None])

            if k % 10 == 0:
                plt.pause(0.001)
        plt.pause(0.01)


    def searching(self):
        """
        Bidirectional Best First
        :return: connected path, visited order of forward, visited order of backward
        """

        self.init()
        s_meet = self.s_start
        count = 0
        while self.OPEN_fore and self.OPEN_back:
            # solve foreward-search
            print(count)
            count+=1
            _, s_fore = heapq.heappop(self.OPEN_fore)

            if s_fore in self.PARENT_back:
                s_meet = s_fore
                break

            self.CLOSED_fore.append(s_fore)

            for s_n in self.get_neighbor(s_fore):
                new_cost = self.g_fore[s_fore] + self.cost(s_fore, s_n)

                if s_n not in self.g_fore:
                    self.g_fore[s_n] = math.inf

                if new_cost < self.g_fore[s_n]:
                    self.g_fore[s_n] = new_cost
                    self.PARENT_fore[s_n] = s_fore
                    heapq.heappush(self.OPEN_fore,
                                   (self.f_value_fore(s_n), s_n))

            # solve backward-search
            _, s_back = heapq.heappop(self.OPEN_back)

            if s_back in self.PARENT_fore:
                s_meet = s_back
                break

            self.CLOSED_back.append(s_back)

            for s_n in self.get_neighbor(s_back):
                new_cost = self.g_back[s_back] + self.cost(s_back, s_n)

                if s_n not in self.g_back:
                    self.g_back[s_n] = math.inf

                if new_cost < self.g_back[s_n]:
                    self.g_back[s_n] = new_cost
                    self.PARENT_back[s_n] = s_back
                    heapq.heappush(self.OPEN_back,
                                   (self.f_value_back(s_n), s_n))

        return self.extract_path(s_meet), self.CLOSED_fore, self.CLOSED_back

    def get_neighbor(self, s):
        """
        find neighbors of state s that not in obstacles.
        :param s: state
        :return: neighbors
        """

        return [(s[0] + u[0], s[1] + u[1]) for u in self.u_set]

    def extract_path(self, s_meet):
        """
        extract path from start and goal
        :param s_meet: meet point of bi-direction best first
        :return: path
        """

        # extract path for foreward part
        path_fore = [s_meet]
        s = s_meet

        while True:
            s = self.PARENT_fore[s]
            path_fore.append(s)
            if s == self.s_start:
                break

        # extract path for backward part
        path_back = []
        s = s_meet

        while True:
            s = self.PARENT_back[s]
            path_back.append(s)
            if s == self.s_goal:
                break

        return list(reversed(path_fore)) + list(path_back)

    def f_value_fore(self, s):
        """
        forward searching: f = h. (g: Cost to come, h: heuristic value)
        :param s: current state
        :return: f
        """

        return self.h(s, self.s_goal)

    def f_value_back(self, s):
        """
        backward searching: f = h. (g: Cost to come, h: heuristic value)
        :param s: current state
        :return: f
        """

        return self.h(s, self.s_start)

    def h(self, s, goal):
        """
        Calculate heuristic value.
        :param s: current node (state)
        :param goal: goal node (state)
        :return: heuristic value
        """

        heuristic_type = self.heuristic_type

        if heuristic_type == "manhattan":
            return abs(goal[0] - s[0]) + abs(goal[1] - s[1])
        else:
            return math.hypot(goal[0] - s[0], goal[1] - s[1])

    def cost(self, s_start, s_goal):
        """
        Calculate Cost for this motion
        :param s_start: starting node
        :param s_goal: end node
        :return:  Cost for this motion
        :note: Cost function could be more complicate!
        """

        if self.is_collision(s_start, s_goal):
            return math.inf

        return math.hypot(s_goal[0] - s_start[0], s_goal[1] - s_start[1])

    def is_collision(self, s_start, s_end):
        """
        check if the line segment (s_start, s_end) is collision.
        :param s_start: start node
        :param s_end: end node
        :return: True: is collision / False: not collision
        """

        if s_start in self.obs or s_end in self.obs:
            return True

        if s_start[0] != s_end[0] and s_start[1] != s_end[1]:
            if s_end[0] - s_start[0] == s_start[1] - s_end[1]:
                s1 = (min(s_start[0], s_end[0]), min(s_start[1], s_end[1]))
                s2 = (max(s_start[0], s_end[0]), max(s_start[1], s_end[1]))
            else:
                s1 = (min(s_start[0], s_end[0]), max(s_start[1], s_end[1]))
                s2 = (max(s_start[0], s_end[0]), min(s_start[1], s_end[1]))

            if s1 in self.obs or s2 in self.obs:
                return True

        return False


def main():
    x_start = (5, 5)
    x_goal = (45, 25)

    bastar = BidirectionalBestFirst(x_start, x_goal, "euclidean",x_start,x_goal)
    
    path, visited_fore, visited_back = bastar.searching()
    bastar.animation_bi_astar(path, visited_fore, visited_back, "Bidirectional-Best-First")  # animation


if __name__ == '__main__':
    main()

简单思路解析:

首先将起点跟终点都放入到一个堆栈内,然后分别对两个栈进行出栈操作,计算周围点位的代价值。对于起点,其周围点的代价值计算公式为:

        return self.h(s, self.s_goal)
        
    def h(self, s, goal):
        heuristic_type = self.heuristic_type
        if heuristic_type == "manhattan":
            return abs(goal[0] - s[0]) + abs(goal[1] - s[1])
        else:
            return math.hypot(goal[0] - s[0], goal[1] - s[1])

即当前点到终点的代价值。

对于终点,其周围点的代价值计算公式为:

        return self.h(s, self.s_start)
        
    def h(self, s, goal):
        heuristic_type = self.heuristic_type
        if heuristic_type == "manhattan":
            return abs(goal[0] - s[0]) + abs(goal[1] - s[1])
        else:
            return math.hypot(goal[0] - s[0], goal[1] - s[1])

然后循环进行出栈入栈操作,直到满足以下条件之一:

            if s_fore in self.PARENT_back:
                s_meet = s_fore
                break
            if s_back in self.PARENT_fore:
                s_meet = s_back
                break

即前向搜索遇到的点在后向搜索中出现过或者后向搜索中的点在前向搜索中出现过。说明前后搜索到同一个位置了,则代表找到了路径。这里self.PARENT_back与self.PARENT_fore分别维护了一个字典,方便查找。

效果:

在这里插入图片描述

与最佳路径优先搜索算法的对比

双向最佳路径优先搜索算法最大的优点就在于它极大程度的降低了搜索的深度,可以很快的完成迭代。前一章我们讲述了最佳路径优先搜索算法,为了查看它的迭代次数,在while里面设置一个打印输出count,每迭代一次就自加一。到结束时可以看到数据已经自加到了947:
在这里插入图片描述
也就是说最佳路径优先搜索算法在寻找起点到终点的过程中一共迭代了947次,而同样的我们使用双向最佳路径优先搜索算法后,同样查看它的迭代次数:
在这里插入图片描述
可以看到双向最佳路径优先搜索算法只迭代了192次。即便它每一次会操作两个字典,将操作次数翻倍,也只有384次操作。比最佳路径优先搜索算法还是要快很多。这也说明了双向最佳路径优先搜索算法在搜索深度上确实要比单向算法更浅,速度也比单向算法更快捷。

参考:

1、搜索优化技巧:双向搜索

2、双向BFS

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/875933.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Rx.NET in Action 第三章学习笔记

3 C#函数式编程思想 本章内容包括 将 C# 与函数式技术相结合使用委托和 lambda 表达式使用 LINQ 查询集合 面向对象编程为程序开发提供了巨大的生产力。它将复杂的系统分解为类&#xff0c;使项目更易于管理&#xff0c;而对象则是一个个孤岛&#xff0c;你可以集中精力分别处理…

table 根据窗口缩放,自适应

element-plus中&#xff0c;直接应用在页面样式上&#xff0c; ::v-deep .el-table{width: 100%; } ::v-deep .el-table__header-wrapper table,::v-deep .el-table__body-wrapper table{width: 100% !important; } ::v-deep .el-table__body,::v-deep .el-table__footer,::v-d…

试卷转电子版怎样处理?分享个好用的扫描转换方法

试卷转电子版是一个常见的需求&#xff0c;可以通过扫描纸质试卷来实现。但是&#xff0c;扫描后的文件可能会有一些问题&#xff0c;例如模糊、颜色失真、文字识别错误等。在这篇文章中&#xff0c;我将分享一个好用的扫描转换方法&#xff0c;可以帮助您快速而准确地将试卷转…

Spring 中 @Qualifier 注解还能这么用?

文章目录 1. 基本用法1.1 指定 Bean 名称1.2 不指定 Bean 名称1.3 自定义注解1.4 XML 中的配置 2. 源码分析2.1 doResolveDependency2.2 findAutowireCandidates 3. 小结 今天想和小伙伴们聊一聊 Qualifier 注解的完整用法&#xff0c;同时也顺便分析一下它的实现原理。 说到 Q…

【运维知识高级篇】超详细的Jenkins教程1(安装部署+配置插件+创建自由风格项目+配合gitlab实现Jenkins自动触发)

Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成的工具&#xff0c;用于监控持续重复的工作&#xff0c; 旨在提供一个开放易用的平台&#xff0c;使软件的持续集成变成可能&#xff0c;是持续集成的核心&#xff0c;可以与其他软件进行协作&#xff0c;例…

为什么骑友对太过商业化的景点如此反感?

一骑友小李最近在社交媒体上分享了他的旅行经历。他去了一个著名的景点&#xff0c;原本期待满满&#xff0c;却发现这个曾经心中的旅行圣地已经变得过分商业化。小卖部、纪念品摊位、过度开发的风景……让他感到十分失望。他的故事引发了骑友们的热议&#xff0c;很多人表示深…

vue.draggable浅尝

介绍 Vue.Draggable是一款基于Sortable.js实现的vue拖拽插件。支持移动设备、拖拽和选择文本、智能滚动&#xff0c;可以在不同列表间拖拽、不依赖jQuery为基础、vue 2过渡动画兼容、支持撤销操作&#xff0c;总之是一款非常优秀的vue拖拽组件。本篇将介绍如何搭建环境及简单的…

网工最常犯的9大错误,越早知道越吃香

下午好&#xff0c;我的网工朋友 我们常说&#xff0c;人要学会避免错误&#xff0c;尤其是对在职场生活的打工人来说&#xff0c;更是如此。 学生时代&#xff0c;我们通过错题本收集错误&#xff0c;提高刷题正确率和分数&#xff0c;但到了职场&#xff0c;因为没有量化的…

2023年京东宠物食品行业数据分析(京东大数据)

宠物食品市场需求主要来自于养宠规模&#xff0c;近年来由于我国宠物数量及养宠人群的规模均在不断扩大&#xff0c;宠物相关产业和市场规模也在蓬勃发展&#xff0c;宠物食品市场也同样保持正向增长。 根据鲸参谋电商数据分析平台的相关数据显示&#xff0c;2023年1月-7月&am…

Live Market:个人如何做跨境电商?带你从0到1了解跨境电商

跨境电商是指通过互联网技术&#xff0c;将商品从一个国家或地区销售到另一个国家或地区的商业活动。为消费者提供更加丰富、优质、实惠的商品选择&#xff0c;促进国际贸易的发展和经济的繁荣。跨境电商还可以帮助企业降低成本&#xff0c;提高效率&#xff0c;提升品牌知名度…

创建一个 React+Typescript 项目

接下来 我们来一起探索一下用TypeScript 来编写react 这也是一个非常好的趋势&#xff0c;目前也非常多人使用 那么 我们就先从创建项目开始 首先 我们先找一个 或者 之前创建一个目录 用来放我们的项目 然后 在这个目录下直接输入 例如 这里 我想创建一个叫 tsReApp 的项目…

【Java】2021 RoboCom 机器人开发者大赛-高职组(复赛)题解

7-8 人工智能打招呼 号称具有人工智能的机器人&#xff0c;至少应该能分辨出新人和老朋友&#xff0c;所以打招呼的时候应该能有所区别。本题就请你为这个人工智能机器人实现这个功能&#xff1a;当它遇到陌生人的时候&#xff0c;会说&#xff1a;“Hello X, how are you?”其…

终端里执行qtcreator命令报错xcb

使用rpm 安装libxkbcommon-x11-0.8.4-3.ky10.x86_64.rpm包

新能源充电桩运营管理平台解决方案含开源代码

标准化产品 、 快速接入 、 自主可控 、 安全合规 、 互联互通 √快速接入通过数据交互协议实现业务交互&#xff0c;提供专业的协议开发文档及Demo代码&#xff0c;助力桩企快速实现适配及开发工作。 √标准化产品通过私有化部署帮助企业快速构建自主可控的充电桩运营管理平台…

后院失火、持续亏损!Mobileye半年报「不回避」竞争压力

"客户在2023年上半年非常谨慎&#xff0c;导致增长率低于正常水平&#xff0c;但我们已经看到下半年回暖趋势&#xff0c;预计下半年交付将比去年同期增长16%&#xff0c;远高于上半年。"这是Mobileye在近日公司半年报发布会上的预判。 公开数据显示&#xff0c;今年…

好用的免费音频转换器大揭秘

你是否曾经遇到过这样的情况&#xff1a;你有一首喜欢的歌曲或者音频文件&#xff0c;但是你的播放器或设备不支持该文件格式&#xff1f;这时候&#xff0c;你需要一款好用的音频格式转换器来帮助你。说到这&#xff0c;你可能会问&#xff0c;“我都不知道免费的音频格式转换…

Java 中的强引用、弱引用、软引用和虚引用

一、继承结构 1.1 四大引用的继承关系 在 Java 中一共有四种引用类型&#xff0c;分别是强引用、弱引用、软引用和虚引用&#xff0c;其中&#xff0c;我们常用的是强引用&#xff0c;而其他三种引用都需要引入特定的 java.lang.ref 才能使用&#xff0c;他们的继承结构如下…

干货分享:制作婚礼请柬的技巧,从零基础起步

在现代社会&#xff0c;婚礼请柬已经成为了婚礼必备的一部分。而如何制作一个个性化的婚礼请柬呢&#xff1f;今天&#xff0c;我们将分享一个简便而可靠的制作方法&#xff0c;那就是使用乔拓云平台。 乔拓云平台是一个可靠的第三方制作工具&#xff0c;提供了丰富的H5模板&am…

教你如何为博客网站申请阿里云的免费域名HTTPS证书

如何为博客网站申请阿里云的免费域名HTTPS证书 文章目录 如何为博客网站申请阿里云的免费域名HTTPS证书前置条件&#xff1a;步骤1 例如阿里云控制台&#xff0c;选择SSL证书步骤2 申请购买免费证书步骤3 创建证书步骤3.1 证书申请步骤3.2 DNS域名验证 步骤4 等待证书审核成功&…

徐明君:品牌定位塑造独特价值与关键差异的新思维

在当今竞争激烈的市场环境中&#xff0c;品牌定位已成为企业生存与发展的关键要素。近年来&#xff0c;随着消费者需求的不断变化和市场的持续发展&#xff0c;品牌定位的内涵和方法也在不断演进。本文将探讨品牌定位的核心概念、作用以及如何有效运用品牌定位策略&#xff0c;…