数据结构 | 第6章 优先级搜索

306 阅读3分钟

6.9 优先级搜索

前面所讲述各算法在功能上的差异,主要体现为每一步迭代中对新顶点的选取策略不同。如BFS搜索会优先考查更早被发现的顶点,而DFS搜索则恰好相反,会优先考查最后被发现的顶点。

每一种选取策略都等效于,给所有顶点赋予不同的优先级,而且随着算法的推进不断调整; 而每一步迭代所选取的顶点,都是当时的优先级最高者。按照这种理解,包括BFS和DFS在内的几乎所有图搜索,都可纳入统一的框架。鉴于优先级在其中所扮演的关键角色,故亦称作优先级搜索(priority-first search, PFS),或最佳优先搜索(best-first search, BFS,不是广度优先搜索的BFS!  )。

为落实以上理解,图ADT(表6.2和代码6.1)提供了priority()接口,以支持对顶点优先级数(priority number)的读取和修改。在实际应用中,引导优化方向的指标往往对应于某种有限的资源或成本(如光纤长度、通讯带宽等),故不妨约定优先级数越大顶点的优先级越低。相应地,在算法的初始化阶段(如代码6.1中的reset()),通常都将顶点的优先级数统一置为最大(比如INT_MAX)——或等价地,优先级最低。

优先级搜索算法框架:

 0001 template <typename Tv, typename Te> template <typename PU> //优先级搜索(全图)
 0002 void Graph<Tv, Te>::pfs ( Rank s, PU prioUpdater ) { //s < n
 0003    reset(); Rank v = s; //初始化
 0004    do //逐一检查所有顶点
 0005       if ( UNDISCOVERED == status ( v ) ) //一旦遇到尚未发现的顶点
 0006          PFS ( v, prioUpdater ); //即从该顶点出发启动一次PFS
 0007    while ( s != ( v = ( ( v+1 ) % n ) ) ); //按序号检查,故不漏不重
 0008 }
 0009 
 0010 template <typename Tv, typename Te> template <typename PU> //顶点类型、边类型、优先级更新器
 0011 void Graph<Tv, Te>::PFS ( Rank v, PU prioUpdater ) { //优先级搜索(单个连通域)
 0012    priority ( v ) = 0; status ( v ) = VISITED; parent ( v ) = -1; //初始化,起点v加至PFS树中
 0013    while ( 1 ) { //将下一顶点和边加至PFS树中
 0014       for ( Rank u = firstNbr ( v ); -1 < u; u = nextNbr ( v, u ) ) //对v的每一个邻居u
 0015          prioUpdater ( this, v, u ); //更新其优先级及其父亲
 0016       for ( int shortest = INT_MAX, u = 0; u < n; u++ )
 0017          if ( UNDISCOVERED == status ( u ) ) //从尚未加入遍历树的顶点中
 0018             if ( shortest > priority ( u ) ) //选出下一个
 0019                { shortest = priority ( u ); v = u; } //优先级最高的顶点v
 0020       if ( VISITED == status ( v ) ) break; //直至所有顶点均已加入
 0021       status ( v ) = VISITED; type ( parent ( v ), v ) = TREE; //将v及与其父的联边加入遍历树
 0022    }
 0023 } //通过定义具体的优先级更新策略prioUpdater,即可实现不同的算法功能

可见,PFS搜索的基本过程和功能与常规的图搜索算法一样,也是以迭代方式逐步引入顶点和边,最终构造出一棵遍历树(或者遍历森林)。如上所述,每次都是引入当前优先级最高(优先级数最小)的顶点v,然后按照不同的策略更新其邻接顶点的优先级数。

这里借助函数对象prioUpdater,使算法设计者得以根据不同的问题需求,简明地描述和实现对应的更新策略。具体地,只需重新定义prioUpdater对象即可,而不必重复实现公共部分。比如,此前的BFS搜索和DFS搜索都可按照此模式统一实现。

之后,以最小支撑树和最短路径这两个经典的图算法为例,深入介绍这一框架的具体应用。

复杂度:

PFS搜索由两重循环构成,其中内层循环又含并列的两个循环。若采用邻接表实现方式,同时假定prioUpdater()只需常数时间,则前一内循环的累计时间应取决于所有顶点的出度总和,即O(e);后一内循环固定迭代n次,O(n2)。两项合计总体复杂度为O(n2)。

“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 11 天,点击查看活动详情