DFS序列、割点与破圈法

DFS序、割点与破圈法

概述

DFS序列是图论中的基础,因为DFS序的变形与延伸算法占有图论的基础。割点与破圈则是DFS延伸的算法或概念。

DFS现在一直被理解为深度优先搜索这个算法架构,其实并不是啊,在多本参考文献中DFS一直以DFS序的形式出现,而所谓的深搜只是浅层次的含义。

什么是DFS序(DFS)?
dfs序,顾名思义,就是dfs深度优先搜索经过的点的序列;所以dfs序具有这几个性质:知道哪个点连接自己、知道是第几个遍历的点等

U.color存储着当前状态

GRAY为正在访问,WHITE为未访问,BLACK为被访问过。

U.π代表了连接它的父亲节点。

U.d表示了第一时间戳,U.f表示了第二时间戳。

我们不妨设图Gπ=V,Eπ),其中

Eπ={v.π,v):vVv.π≠NIL}

Eπ为树边,Gπ的前驱子图为Gπ=VπEπ))

每个结点的初始颜色都是白色,在结点被访问时为灰色,其邻接链表被扫描后变成黑色。

第二时间戳v.f记录了搜索完成对v的邻接链表扫描的时间(被标BLACK时)

而第一时间戳v.d记录了v被发现时的时间(被标GRAY时)

显然对于每一个结点u

u.d

而对于输入图G既可以是有向图,也可以是无向图。

DFS序的过程与DFS

上伪代码过程(参考见算法导论P350

DFSG

  For each vertex u G.V

     u.color=WHITE

     u.π=NIL

  Time=0

  For each vertex uG.V

If u.color==WHITE

   DFS_VISIT(G,u)

DFS_VISIT(G,u)

  Time++

  u.d=time;

  u.color=GRAY

  For each vADJ[u]

     If v.color==WHITE

        v.π=u

        DFS_VISIT(G,v)

  u.color=BLACK

Time++

u.f=time

其实过程的描述就是将G中的u点深搜,只要u未访问就深搜u,更新出入时 间戳以及u临边v的过程。

DFS

先来看一组样例(详见算法导论P354

 DFS序列、割点与破圈法_第1张图片

问:求解所点的遍历后的状态。

(这实在太简单啦!)

q[.d=1 .π=NIL .f=16 .color=BLACK]

s[.d=2 .π=q  .f=7 .color=BLACK]

v[.d=3 .π=s .f=6 .color=BLACK]

w[.d=4 .π=v .f=5 .color=BLACK]

t[.d=8 .π=q .f=15 .color=BLACK]

x[.d=9 .π=t .f=12 .color=BLACK]

z[.d=10 .π=x .f=11 .color=BLACK]

y[.d=13 .π=t .f=14 .color=BLACK]

r[.d=17 .π=NIL .f=20 .color=BLACK]

u[.d=18 .π=r .f=19 .color=BLACK]

是不是看上去比较混乱?

为了跟好精确的标出(理解)DFS序,有一种使用树形结构的存储方式(我们暂且可以叫它DFS树),它将结点编号作为树结点,将出入时间戳标成区间形式([d,f]

,如下图。

 DFS序列、割点与破圈法_第2张图片

 

  我们发现了什么???

  任意两点间的时间戳区间有三种情况:

1. u包含v,则uv的祖先

2. v包含u,则vu的祖先

3. uv不是包含关系,则uv不在同一条树枝上。

同时,在DFS上的都是合法的树边,不存在有环的边。

也就是说,只要我们求出了DFS树,我们就可以求出DFS的合法边以及任意两点的关系了。

代码(DFS序):

Void DFS(int u)

{

    For (int i=1;i<=G.len;u++)

{

       U[i].color=WHITE

       U[i].path=0;

    }

    Time=0;

    For (int i=1;i<=G.len;i++)

If U[i].color==WHITE

      DFS_VISIT(i);

 }

Void DFS_VISIT(int u_xb)

{

  Time++;

  U[u_xd].d=Time;

  U[u_xd].color=GRAY;

  For (int i=1;i<=ADJsum;i++)

     If ADJ[i].color==WHITE

{

        U[ADJ[i].path]=i;

        DFS_VISIT(ADJ[i]);

     }

    U[u_xd].color=BLACK;

Time++;

U[u_xd].f=Time;

}

 

割点

割点的含义就是一个图中除去某些点图就不连通,将这些点称之为割点。也就说割点是一张图的开关。

求解割点

其实求解割点十分简单,我们不难发现,直接一遍DFS,求解DFS树,在DFS树中遇到树边继续做,剩下的边则不用做(称之为回边),直接跳过。这种算法可以过题。

当然,求解割点也可以用Tarjan算法。

Tarjan算法与求解割点

介绍一下Tarjan算法

它是由美国科学家Tarjan发明的算法,用于解强连通分量的线性时间而诞生的算法。同时你需要知道——强连通分量到底是啥?

涨知识:强连通分量即在有向图中存在一个子图,子图中每两个点均满足强连通,我们把这个子图叫做强联通分量。(不需要我再解释强联通吧)

Tarjan算法就是使用DFS来实现将强连通分量在一棵搜索树上的子树。所谓的图,其实就是这棵树。

我们先声明一下,Tarjan的每一个结点都有两个通用代号:

一个是DFN存储时间戳,另一个叫LOW存储最小的子树的根His fathers time它父亲的时间戳

我们可以使用堆栈来存储这个结构,只要每次new一个点就进栈,有出度就出栈,直到找到最后一层。

那么,如何使用Tarjan求解割点呢?

其实也很简单,我们分两种情况

对于根节点:只要判断不是陈独秀(有两个及以上的树枝)就OK(是割点)。

对于非根节点:

设当前边为(u,v)

Tarjan(int u,v)

{

Low[u]=Dfn[u]

if color[v]==WHITE

{

     DFS(v);

       Low[u]=min(Low[u],Low[v])

}

Else(默认u非v祖)

Low[u]=min(Low[u],Dfn[v])

}

为什么上代码?

道理很简单,只要Low[u]>=Dfn[u],u一定是割点。

破圈法求解MST

对于最小生成树(MST)大家一定不陌生,由PrimKruskal俩大佬发明的算法被我们今天所广泛利用,已经少有人知破圈法求MST(有人会打我),难道是因为国产算法不热?

破圈法由我国科学家管梅谷提出来的,在国际图论界有极高评价。

破圈法是一个动态的算法,相对于Primkrukal更有发展空间,对于pprim)和kkruskal)都是增加点或边(避圈),而破圈法提出了删边的理论:见圈破圈。

破圈法的思想步骤有三步:

1.  找到回路。

2.  删除一条除割边以外权值最大的边。

3. 重复以上操作,直到没有环为止。

破圈法有神马作用呢?

当然有作用,可以解动态增删边的问题,时间复杂度也不大。

可惜这种问题对于DFS序是无法解决的,为什么?

DFS序是定死的,再加一条边或删去。。。

重新做一遍吧。

当样例再加两个0

BOOM

(资料不多啊,所以没有代码,敬请谅解)

参考文献

算法导论原书第3版(DFS深度优先搜索)

全网最!详!细!Tarjan算法讲解。

破圈法-百度百科。

割点-百度百科。

转载问题

欢迎转载!转载须标明出处与作者。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(算法,图论,MST,DFS,割点与割边)