BFS DFS 的理解与应用 --- 算法导论读书笔记

来源:互联网 发布:能耗监控分析软件 编辑:程序博客网 时间:2024/05/21 12:41

先摘抄一段概述的话:

        搜索一个图是有序地沿着图的边访问所有顶点。图的搜索算法可以使我们发现图的很多结构信息。许多图的算法在开始时,都是通过搜索输入的图来获取结构信息。另外还有一些图的算法实际上是由基本的图搜索算法经过简单扩充而成。因此,图的搜索技术是图算法领域的核心。


BFS:

        是Prim最小生成树和Dijkstra单源最短路的原型。

        用三种颜色表示顶点:白色(未访问),灰色(已访问但子节点或许有未访问的白节点),黑色(已访问并子节点也已访问)。以此确保节点只被访问一次。

        用一个队列控制访问顺序。


代码模版:

BFS(G,s)

//初始化for each u from V[G]-{s}  do color[u] <- WHITE       d[u] <- infinity       pre[u] <- NILcolor[s] <- GRAYd[s] <- 0pre[s] <- NILQ <- empty//开始bfsENQUEUE(Q,s)while Q != empty  do u <- DEQUEUE(Q)    for each v from Adj[u]      do if color[v] == WHITE           then color[v] <- GRAY                   d[v] <- d[u] + 1                   pre[v] <- u                   ENQUEUE(Q,v)      color[u] <- BLACK

        其中d[]给出了最短路径长度,pre[]给出路径,也可以pre[]构建出广度优先树(最短路径树),则此时d[]就代表了深度。
        显然,这代码本身并没有对图顶点遍历,它只能遍历s所在的连通分量。如果实在要遍历,可以参照下面的DFS,在外层加一个循环,对白色节点进行BFS(G,s)操作,可以生成森林。但是从常见的用法而言,基本不这么做。通常我们只是以此找最短路。




DFS:
        一般来讲,遍历就用的DFS,也是我们常说的“回溯法”,通常DFS被用在另一个算法中的子程序,比如做一个拓扑排序(按节点完成遍历的时间的倒序放入,或者记录入度,在遍历的时候依次放入入度为0的节点),是要求遍历的。所以在代码中我们的版本为遍历。同样是三种颜色,可以用递归,也可以栈控制访问顺序(毕竟递归也是依靠栈实现的,而我们使用栈存节点可以使得程序开销更小)。

        同时,用两次DFS可以分解强连通分支(很多有向图的算法都是在分解强连通分支之后,再用分治分别处理各个分支)。第一次DFS做拓扑排序,第二次DFS按拓扑排序的顺序,分解出的深度优先树则为强连通分支。


代码模板:
DFS(G)

//初始化for each u from V[G]  do color[u] <- WHITE     pre[u] <- NILtime <- 0//遍历for each u from V[G]  do if color[u] == WHITE    then DFS_VISIT(u)

//递归版DFS_VISIT(u):color[u] <- GRAYtime <- time + 1d[u] <- timefor each v from Adj[u]  do if color[v] == WHITE        then pre[v] <- u             DFS_VISIT(v)color[u] <- BLACKtime <- time + 1f[u] <- time

DFS_VISIT(u)://栈版S <- emptycolor[u] <- GRAYtime <- time + 1d[u] <- timeENSTACK(S,u)while S != empty   for each v from Adj[u]      do if color[v] == WHITE            then color[v] <- GRAY                pre[v] <- u                time <- time + 1                d[v] <- time                ENSTACK(S,v)                u <- TOPSTACK(S)   time <- time + 1   f[u] <- time   color[u] <- BLACK   DESTACK(S,u)    

        这里我们加上了时间戳,分别为开始访问时间d[],结束访问时间(访问了所有子节点)f[],即为变灰时间和变黑时间,在栈版中还对应入栈时间和出栈时间。时间戳可以用于一些后续的应用,比如判断节点v是否为u的后裔(v为u的后裔当且仅当d[u]<d[v]<f[v]<f[u])。当然,在栈版中,只需要两种颜色,白和非白,即是否访问,而是对于访问了的节点否在栈中则天然地说明了它是否访问完毕。

        在DFS过程中可以同时将边分类,以用于以后的算法(如无回路就是无反向边)。首先我们给出四类边的定义:
  1. 树边(tree edge),是深度优先森林中的边。
  2. 反向边(back edge),是树中,连接v到祖先u的边。
  3. 正向边(forward edge),是连接u到后裔v的非树边。
  4. 交叉边(cross edge),其他类型的边,即连接两个非祖先后裔关系的点的边。
       分类方法:根据该边被第一次探寻到时,所到达顶点的v的颜色
  1. 白色表明树边
  2. 灰色表面反向边
  3. 黑色表面正向边或交叉边

       同时,DFS中无向图只有树边和反向边。

原创粉丝点击