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 天,点击查看活动详情”