数据结构与算法笔记:图搜索之BFS详解

图搜索Graph Search的分类

  • BFS广度优先(宽搜) !!!本文详解!!!
  • DFS深度优先(深搜)

广度优先搜索BFS

  • 我们可以用比喻来说明广度优先搜索算法
    • 在一片草木枯黄的深秋草原上,在草原的某一处出现了一处野火
    • 一开始的时候野火集中于一点之上,在这点野火耗尽当前植被变成灰烬之前点燃了周围的植被
    • 比如节点s是初始火种,假设我们手中有一个秒表,每过1秒,我们的大火会向外迈进一步
    • 这个过程只能向外,不能向内,因为只能点燃植被,不能把灰烬点燃
    • 蓝色的点是即将变为灰烬的点,红色的点是刚被点燃的点,灰色的圆形或圆角矩形是一个前锋面
    • 所谓前锋面是火焰向外传播的一个面,frontier
    • 之后,每一处的植被都按照同样的模型向外去蔓延, 点燃外层的邻居,前锋面越来越大,最终整个草原燃烧殆尽
    • 这个过程是非常自然的,也就是所谓的道法自然,模拟自然的一个过程
    • 整个过程,如下图所示

在这里插入图片描述

  • 任何图结构的模型,只要指定一个节点,比如上图中的s点作为"树根"
  • 我们可以把整棵树(图)摊平在某个桌面上,接下来就要开始进行模拟计算
  • 如果一个点自己是点燃状态的,那么它接下来就通过一个边去点燃外部的邻居
  • 如果邻居是灰烬状态不会被点燃(不会向内部传播),就是这个过程可以点燃整片草原
  • 这个方法可以针对s点而言可达的,连通的部分全部访问一遍,这种访问的特点是不重不漏
  • 这个方法被称为遍历,也就是traverse或traversal

算法框架实现

template <typename Tv, typename Te>

// v是初始点,clock是读秒器或称为计时器
void Graph<Tv, Te>::BFS(int v, int & clock) {
    // 1. 初始化
    // 1.1 内部引入一个队列Q, 任何一个点,初始化的时候都是UNDISCOVERED状态,初始的时候v指定为DISCOVERED状态
    // 1.2 换种说法:UNDISCOVERED是未燃烧状态,DISCOVERED是燃烧状态
    // 1.3 初始点入队
    Queue<int> Q; status(v) = DISCOVERED; Q.enqueue(v);
    // 2. 处理当前的前锋面上的所有元素
    // 2.1 进行循环处理, 循环终止条件是队列变空, 也就是燃烧殆尽
    while(!Q.empty()) {
        // 反复地,如果不空, FIFO, 并且添加一个时间标签,这里的dTime, d暗示DISCOVERED
        // 这时候一个元素独立的占据1s,其实同一个前锋面上的点在自然环境中是同时燃烧的
        // 因为我们没法做到, 所以为每一个元素添加一个时间标签,这是一种蹩脚的体现
        int v = Q.dequeue(); dTime(v) = ++clock; // 取出队首顶点v
        // 考察v的每一邻居u,这个for循环是这个v点的使命,-1<u, 表示u不再是邻居了
        for(int u = firstNbr(v); -1 < u; u = nextNbr(v,u)) {
            // 根据u的状态,分别处理
            if(UNDISCOVERED == status(u)) {
                // 若u尚未被发现,则
                status(u) = DISCOVERED; // 当前邻居标记成DISCOVERED状态
                Q.enqueue(u); // 发现该顶点,将该邻居入队尾,进入前锋面的范围
                type(v,u) = TREE; // u之所以会烧起来是被内部的邻居v点燃的, 在将来生成的树中,火传递的方向对应的就是边, 引入树边TREE EDGE
                parent(u) = v; // u要把v作为自己的parent
            } else {
                // 若u已被发现(正在队列中),或者已经能访问完毕(已出队列),将(v,u)归类于跨边
                type(v,u) = CROSS;
            }
        }
        // 2.2. 此时,当前顶点访问完毕,也就是v变成了灰烬并且处理完成v所有的邻居
        status(v) = VISITED;
    }
}
  • 代码实现可以有很多风格,每种都会有细微的差异,这里的算法是基于c++模板构成的
  • 这个算法是模拟自然的过程,最重要的模拟是如何模拟前锋面
  • 只要我们模拟出了前锋面(一圈一圈的,一个单位时间,对应一个半径的增长),就模拟出了整个燃烧的过程
  • 目前,我们没有什么好的并行机制,将任何时候的前锋面模拟出来,我们需要一个数据结构
  • 我们需要把所有前锋面上的所有点都收容进去,但是我们不可能理想的并行的去模拟
  • 实际上,前锋面上的每个火源都是各向同性,互不干扰,高效地往外传递,但是我们的计算必须要一个点一个点的处理
  • 这些前锋面上的火源表面上看都是相等的,但是我们需要人为的指定一个优先级,比如指定一个点A
  • 在处理这个点A的时候,我们要模拟它在燃烧模型中的行为,它的外层邻居如: A 1 ′ , A 2 ′ , A 3 ′ , . . . A'_1,A'_2,A'_3,... A1,A2,A3,...将被点燃
  • 这个时候就有意思了,当它的这些外层邻居都点燃了, 点A就会成为灰烬,就可以被删除了
  • 而且理想模型下的点A的同辈:B,C,D…这些在前锋面上的同辈兄弟节点都没有了,都可以被删除了
  • 这个时候,就形成了第二个前锋面,但是这个理想型的并发模型,我们无法实现
  • 所以,当点A变成灰烬之时,就可以组织第二个前锋面了,我们让点A的外层邻居先进来
  • 同样的,点A的同辈兄弟节点按着这个模型依次填充第二个前锋面
  • 我们可以知道,第一个前锋面上的所有点A,B,C,D…这些点在构成前锋面的时候是FIFO
  • FIFO(Fist In, Fist Out)先进先出,所以,我们需要一个队列Queue来组织每一个前锋面
  • 里外前锋面是这样用队列来组织的
    • 前锋面上的每一个点都排成一个队列,当队头元素A点燃尽的时候, 会被dequeue出队
    • 之后,被点A点燃的当前外层邻居enqueue入队,之后再以同样的方式处理B,C,D…
  • 模拟图如下所示

在这里插入图片描述

  • 上图橙色圆角框代表的是队列,入队的元素是红色的,出队的元素是蓝色的,并且用橙色圆角矩形存储
  • 需要注意的是,上述过程中举个例子来说明,当点燃到a的时候,a需要去寻找它的外层邻居,如上图有e和c
  • 但是c早已经在s化成灰烬前入队了, 也就是说c是s的外层邻居
  • 所以到a的时候,有效的外层邻居只有e, 因为a-c这条路径会构成一条CROSS边
  • 绿实线是TREE EDGE,黄虚线是CROSS
    • 上图中可见, 从s-d, s-c, s-a, a-e等绿色的线条都是TREE EDGE
    • 为什么叫黄色的叫CROSS, 因为它试图连接出一个环出来,这个在树中是禁忌的
  • BFS大概就是上述这么一个过程

BFS: Shortest Distance/Path 最短路径问题

  • BFS有个特点,叫做最短距离,这个和之前我们聊过Dijkstra算法(比较的复杂)有类似之处
  • Dijkstra算法更复杂的原因是图中的每一条边都是有权重的,从某一点开始到其它点的最短距离以及相应的路径
  • BFS的简单在于各向同性,每条边的权重相等,每条边都对应一单位的时间,这种情况下,这个最短距离是怎样的呢
  • 同Dijkstra算法一样,我们也可以使用同样的模拟,将BFS图中可燃烧的点作为珠子,以边作为丝线,这些边都是等长的
  • 把这个图平摊在桌子上,拎起一点如s点,一秒一秒的匀速向上提起,在这个过程中,所有的这些点在这个图的意义下
  • 按照它到s的最短距离,依次的被抬离桌面,因为我们的边是等长的,所以在同一个前锋面上的点会同时被拎起来
  • 第一圈(圈即前锋面),第二圈,第三圈… 这样,按照这个过程,我们得到了一棵以s为树根, 绿色为边的BFS Tree
  • 这棵树中每一条边中都代表火焰的有效传递,从起点s(火源开始)到任意一点(我们暂且称为t点),相对于t点来说
  • 连接s到t的这条被拎起来的树中路径,都是t到树根s的最短路径,如下图所示,灰色都是Cross边,绿色是燃烧路径

在这里插入图片描述

  • 如上图所示,对于t点来说,起点是s, 从t到s的最短路径经过b点,那么对于b点来说,到起点s的最短路径
  • 一定是以b为起点,t-s路径的前缀: b-s, 燃烧模型中的火焰会沿着最短路径进行传播
  • 上述平摊拎起模型中绷直的绳子也一定是沿着最短路径来绷直,也就是火焰的传播方向

BFS: Bipartite Graph (Bigraph) 二部图

  • BFS的一个应用就是所谓的二部图,这个和最小支撑树的时候的"割"的概念很像
  • 如果有一个图存在某一个割,而且使得在这个割的意义下,无论在蓝边的这一部分还是红边的一部分
  • 内部的点之间没有连边,两部分唯一的连边,就是连接红的和蓝的,就是跨越这两个之间的所有的那些桥
  • 每一条边都是在这个割的意义下的那些跨边或者叫做桥,可以用磁铁南北极来比喻,异极相吸,同极相斥
  • 可以把蓝色理解为南极, 红色理解为北极, 蓝色永远不会和蓝色连边,红色永远不会和红色连边
  • 这就是一个二部图,好,现在的问题来了,如果有这么一个图给你,最开始的时候是乱的,分不清所有的蓝和红
  • 怎么来断定这个图是一个二部图,也就是说它存在这样的一个割,能够把红的和蓝的,两个截然划分
  • 只有它们互相之间的连边(包括孤零零的未与任何异色连接的点这种情况),只是不能允许同色相连,怎么判断呢?
  • 这时候,我们就要用到我们的BFS模型,如下图,左边是二部图,右边是BFS模型

在这里插入图片描述

  • 方法比较简单,就是挑任何一个点, 比如上图中的s,不管它是红的还是蓝的,接下来,做一个BFS
  • 就像刚才我们拎着网兜一样,随着时间的流逝,第1秒,第2秒… 对应着一环(环即前锋面), 二环, …
  • 这些绿色的边会逐个绷直,他们会被拉起来,按顺序离开桌面
  • 所谓的一环上的那些边,他们将会在第1个单位的时间,那个时刻,滴答响的那一声会被拎起来离开桌面
  • 这些边是到点s的最短距离,这些边上的点,比如上图标注的一环上的蓝色的点,如果真的将来能够构成一个二部图的话
  • 一定会跟s点异色,就像图上的红的和蓝的一样,其实一开始的起点s的指定是无所谓的,不失一般性,可以把它指定为红的
  • 因为二部图颜色可以互换的,如果真的是一个二部图的话,接下来被拎起来的点就一定是异色的
  • 如上图所示,一环上的一个蓝色的点被点着了,它又会去点燃别人,之所以能够点燃别人,是因为它有边相连
  • 而既然它有边相连,如果整体是一个二部图的话,所有的边都应该发生在不同的两部之间,也就是异色的点之间
  • 如果二环上的一些红点是被这个一环上某一个蓝色的点着的话,而整个图确实又是一个二部图的话
  • 那么这些在二环上的点唯一的可能就是它是红色的点,其他的也是如此,举一反三
  • 如果最开始是红的话,那么接下来,就会在下一秒钟点燃所有的蓝色,而蓝色点在下一秒钟又会点燃红色的点
  • 不断重复下去,红蓝,红蓝这样在秒数的奇偶意义下不断的翻转
  • 好,现在问题又来了,任何图都能做BFS,但任何图未必都是二部图,我们需要一个判定的准则
  • 其实,在这里我们除了要看绷直的竖边(绿色的边),现在最重要的是看那些跨边(灰色的边)
  • 因为绷直绿边的最短性,关于灰色的跨边,在这个环路的意义上来说,它不会跨太远
    • 比如说可能有的会在同一环上的跨边,就像刚才一个正在燃烧的点想要点着它的一个邻居
    • 而那个邻居也正在燃烧,这是同一个环路上的点,我们叫做环路线,它们都是同色的
    • 也可能,有一个点想要点燃在和自己同一环路线上的邻居刚刚点着了一个点
    • 比如一环上有 A , B A, B A,B两点, 二环上有 A ′ , B ′ A', B' A,B两点, 理论上 A A A会点燃 A ′ A' A, B B B会点燃 B ′ B' B
    • 但是 A A A想点燃 B ′ B' B, 实际上是无法操作的,这时候就会构成一个跨边
    • 虽然在这个地方来说这两条边 A A ′ AA' AA A B ′ AB' AB是一样长,但在我们的算法里头,它是重复点的
  • 所以我们的Cross边实际上分为两类
    • 一个是用环路上的,点燃自己同类的同一辈分的那些点,如 A B AB AB,这是同色的跨边,在二部图中是严格被禁止的
    • 另一个是试图去点燃本不该点燃的它下一层的点,如 A B ′ AB' AB,这是异色的跨边,在二部图中是允许的
  • 有没有可能会点燃更多的点,比如说一环的点去点燃三环和四环以及以上的点,答案肯定是不会的
    • 在BFS的机制上,不管是自然的火的燃烧过程,还是网兜提拉离开桌面的模拟过程都不会这样的
    • 因为如果真有那样的话,那这个点和它跨环点燃的那个点,就不应该和自己多两环,三环这样,而必然只能多一环,因为最短路径的性质
  • 因为 A B AB AB这类跨边是严格禁止的,一旦发现这种同一环路上的跨边,算法就立即被终止
  • 这时候可以断定,这个图是不能够像二部图这样切开的,所以它就不是一个二部图
  • 反之,如果一直都能做下去,一直做来做去,都没有这种环线上的这种边,那么它自然也就构成了一个二部图
  • 以上是一个充要条件,虽然我们上述推理证明都不甚严格,但是其结果是正确的,也是很容易理解的

Graph/Tree: Eccentricity/Radius/Center/Diameter

  • 对于图来说,还有一些概念比较重要
    • 1.Eccentricity 偏心距
    • 2.Radius 半径
    • 3.Center 中心
    • 4.Diameter 直径
  • 上述的概念是在圆中的概念,我们可以把它应用在图内
  • 基于BFS的平摊桌面模型,它的骨架实际是一棵树,节点之间的一些跨边被我们扔掉了
  • 这个时候对于任何一个节点来说,我们都可以来讨论他的偏心距(Eccentricity)
    • 如果一张图上的中心点在某个节点上,比如O点,对于任意一个节点A来说,它都有个偏心距
    • 直觉上,我们认为的偏心距应该是|OA|的距离,但是在图上并不好由此来测度,我们需要找一个替代的东西
    • 我们可以以A为起点做一趟BFS, 就相当于从A点扔一个火源,让其自行燃烧
    • 这时候它会一圈一圈地,就像在一个平静的湖面上一石激起层层浪一样形成涟漪
    • 这个涟漪传递出去的过程,其实也在计时,并且在计距离,它们两个可以理解为一个概念
    • 我们会知道这个涟漪沿着一个方向最终平复的那个位置,一定是这个图中最外层的一个节点,我们可以记为T
    • 从A到T是沿一条最短路径过来的,相对来说消耗的时间也是最短时间
    • 而整体来说,在向周围传播的过程中,因为位置和最短传播距离的原因,周围的一些地方会更早的被平息下来
    • 我们把最后一个被平息下来的点,在这里我们不失一般性,可以记为T点
    • 我们就把这样的一段传播距离,按照BFS模型,从A到T的传播距离,就记做偏心距(Eccentricity)
    • 同时我们可以看到越是偏离图的中心,它的偏心距越长,这就是我们在图上的偏心距,一种替代方案
    • 所以,任何一个点只要对它做一次BFS就会计算出它的偏心距
  • 同样的,我们可以讨论图上的中心(Center)
    • 从人类直觉上来说,圆的中心就是最中心的那个点
    • 对于图来说,我们可以认为偏心距最小的点就是图的中心
    • 所以,对于图来说,它的中心很可能并不唯一
    • 一般的来说在一个普通的图中,它的Center可能不止一个,为什么呢?
      • 在这里每个点的偏心距都给他算出来,我们可以先验证一下,比如下图1中偏心距为4的点,至少要4s才能把离自己最远的最后一个点点着
      • 偏心距为5的点,需要5个单位的时间(5s),能够烧完全图
      • 偏心距为3的点,只需3秒钟就把整个图烧完,偏心距为3的点是最小的偏心距,因此也是下图1的图中心有三个
      • 如果一个图是一个匀称规整的环形结构,那么它的中心点的个数取决于节点的个数,即环上的所有点都是中心点
      • 这样来说,一个图的中心点个数一般是<=n(n为节点数),而正规的树(不会构成环路)的中心点的个数,一般是<=2,如下图1,2,3所示

在这里插入图片描述

  • 而同样可知,图中心点的偏心距就是图的半径(Radius)
  • 最后,我们需要了解一下如何找到图中的直径(Diameter)
    • 对于圆来说,它的半径恰好是自己直径的一半,反过来说直径是半径的两倍
    • 但是在图中的直径并不是这样的,至少不见得总是自己半径的两倍,那什么是图的直径呢
    • 其实就是图中距离最长的两个点之间的距离(或者说它们之间的那条通路)
    • 在实际中,我们讨论直径更多的应用于树结构上,毕竟树是一种特殊的图结构
    • 我们之前讨论过二叉树的直径,也就是分而治之
      • 根据树根,将一棵树分成两棵左右子树,我们分别计算左右子树的直径,这里要递归进行计算
      • 然后树根去计算经过自己连通左右孩子的直径(其中左右孩子要确保各自的深度最大,也就是左右子树的高度)
      • 找出三者的最大值即为该二叉树的直径: d = m a x { d L , d R , h L + h R + 2 } d = max\{ d_L, d_R, h_L + h_R + 2 \} d=max{dL,dR,hL+hR+2}
      • 注意,上面的2是树根连接左右孩子的2条树干(枝);L,R分别表示左右;d表示直径;h表示高度,这里很容易理解
    • 如果不是二叉树,而是一般的树,这个分而治之的方法可行么
      • 不失一般性,我们可以看下三叉树,每个孩子作为下面子树的树根
      • 理论上这里可以分而治之来处理,逻辑上可以想得通,但是写起来非常麻烦,时间也不好控制
    • 那么BFS能否取而代之呢,算法是这样的
      • 从树的任何一个点,如s点来开始做一个BFS, 涟漪一圈一圈的传递出去,最终前锋面会灭掉,不再能传播,归于平静
      • 我们需要计算在前锋面灭掉的最后一个点, 我们记为u, 接下来,把u作为火种,再进行一次BFS,找到u的灭点v
      • 这里灭点的解释:在BFS模型中, u是s的灭点,v是u的灭点,s-u是s的偏心距,u-v是u的偏心距,这样很容易理解
      • 在这里u-v就是这棵树的直径(Diameter),其大小也就是u点的偏心距,其证明过程如下图
      • 这样只需要2次BFS即可求出一般树的偏心距,其时间复杂度也是很低的
      • T ( n ) = 2 ∗ O ( n + e ) T(n) = 2 * O(n+e) T(n)=2O(n+e), n是所有的点, e是所有的边
    • BFS找到直径的算法原理,证明如下图所示
      • 1 ) 原理
      • 做两次BFS,第一次BFS从s出发终于u点
      • 接下来以终点u作为起点,又做一次BFS,假设它终止于v
      • 这样u-v之间的路径就是直径
      • 如图下图1所示
      • 2 ) 证明
      • 我们知道任意一棵树一定会有直径,也不见得只有一条,应该说至少有一条
      • 这里使用反证法,如果原理是错的,假设下图x-y之间的路径是树的直径,它经过t点,如下图2所示
      • 这个路径可能很长,中间点省略掉了,只画了关键几个点
      • 这样的话,从s出发,做了一次BFS,最终终止于u,还是从u出发做BFS都会跳过x-y路径
      • 那么,s做的BFS到u的通路是否和所谓的x-y的直径存在交点:要么有,要么没有
        • 如果有,不失一般性,如s-t-u这样的通路,而t是x-y上的一点,如下图3所示
          • 在s做BFS的时候灭点是u,前锋面一定波及到x-y路径
          • 显然,s-u > s-x 并且 s-u > s-y, 显然 t-u > t-x 并且 t-u > t-y
          • 这时候,x-u > x-y 并且 y-u > x-y
          • 所以,x-y是直径,显然不合理!
        • 如果没有,那么x,y所在的树不会落在s做的BFS中的u所在的子树中
          • 也就是说,u所在子树和x,y所在子树的共同祖先是s, 如下图2所示
          • 只有这样,才会出现s-u和x-y没有交集,但是作为连通图的一部分
          • 当s在做第一次BFS的时候u是最终的灭点,所以会经过x-y上的任意一点, 也就是说会波及到这条路径
          • 不失一般性,我们可以认为s在做BFS的时候,会经过t点,如果x-y是直径,按照下图3
          • u作为最后出队的一点,也是最远的一点,这个最远不仅相对于x,也相对于y最远
          • 也就是说 s-u > s-x > t-x 并且 s-u > s-y > t-y
          • 这样的话 u-x > x-y 并且 u-y > x-y
          • 如此,x-y是该树的直径,显然是荒谬的!
      • 综上所述,从任意一点做两次BFS找到的路径确实是该树的直径

在这里插入图片描述

  • 总结一下
    • 计算图中任何一点的偏心距,只要对其做一次BFS即可找出
    • 计算图中的中心点,需要计算图中所有点的偏心距,找到最小的偏心距所对应的点
    • 计算图中的半径,中心点所对应的偏心距即为半径
    • 计算树(特殊的图)中的直径,任意一点,进行2次BFS所得到的路径

Knights of the Round Table 圆桌骑士

  • 圆桌骑士案例中关于BFS应用,关于圆桌骑士的故事
    • 亚瑟王和骑士聚会,骑士平常的时候是生活在不同的区域
    • 国土是由一个矩阵所构成的,矩阵是由一格一格的土地构成
    • 我们所说的圆桌代指聚会所用的圆桌,国土是矩形的,两码事,这里特别说明一下
    • 矩阵中的空白处,没有画出来的地方,可以认为是沼泽地,山区等,不能落脚
    • 我们假设有很多个骑士,亚瑟王希望把他们召集到一起,某一天来开个会
    • 显然这里有一个原则,就是希望能找到一个公共的一个聚会的位置
    • 从而使得他对所有骑士的召唤能够在最短的时间内实现
  • 这时候我们需要分析如何实现这类算法
    • 不失一般性,我们先说就有两个骑士,如何召唤他们
    • 这个问题本身就是可以转换成图的搜索问题,而且就是BFS
    • 我们假设骑士就像国际象棋里的马,在这样一个矩阵中
    • 一个马从任何一个位置出发,要到任何地方去,不能任性为之
    • 国际象棋和中国象棋的马是一个走法,“日” 字,只有从日字的一个角到另一个角才是正确的走法
    • 如果两个骑士分别是A,B,那么走到指定的聚会地点它们必须走日字型到达该点,而且每走一步都为一个日字型
    • 亚瑟王为了骑士更快的聚到一起,应该选择最合适的地点作为聚会地点,也就是该点可以让所有骑士所走的总步数最小
    • 这里基本的计算是每个骑士都要找到关于该点最短的路径,在这里我们知道这个最短路径就要用到我们的BFS
    • 然后在这些最短路径中找到最长的路径,因为一个人或几个人聚会不是聚会,大家必须全了才叫聚会
    • 在这些最短路径中找到最耗时,最长的路径,所花费的时间才是聚会的成本
    • 所以方法是任何两个马,比如A,B,各自去计算一下到达任何一个拟定的聚会的位置,最快需要几步
    • 有意思的是离自己最近的点,未必一步可以到,因为要走日字型,这个很好理解。
    • 如下图中A周围的点中有相应的数字,这个数字就是A点到达该点需要的步数,这里的计算就是我们的BFS模型
    • 很好想象这个模型是怎样铺展开的,这个蔓延不是像火燃烧那样,而是走马步蔓延,其邻居是马字型的邻居
    • 这样走着走着,早晚一天会走无可走,前锋面会停下来,这时,还有一些格子是幸运的,没有被马踏过
    • 如下图,绿色x格子就是马到不了的格子,自己可以去验证一下,而蓝色的可达地点也有一个最早到达的时间
    • A和B两位骑士都会得到这么一个图,也就是2个骑士有两张图
    • 亚瑟王在选地址的时候,要考虑2个骑士如何最快到达,在他拿到了2张图之后来进行衡量
    • 他会怎么做呢,实际上,亚瑟王很聪明,它会把两张图合并起来
    • 在每个格子上基于2张图取一个最大的值,比如最底部的4旁边的格子,两张图分别是1,5, 最后取值为5
    • 所有格子在自己图上取最小的,合起来后取最大的,这个很好理解,最后的一步是
    • 亚瑟王在这张合并的新图上找到值最小的格子,这个格子就是两位骑士最终聚会的地点
    • 如图中的黄色2格子,这个格子就是当前聚会模型中最优的点,只需要2步的单位即可召集起2位骑士
    • 同理,3个,4个,10个,100个,1w个骑士, 原理是一样的,都是这么来处理
    • 这里最基本的算法是BFS

在这里插入图片描述

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值