Gabow算法

有向图强连通分量的定义:在有向图G中,如果两个顶点vi,vj间(vi!= vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(stronglyconnected components)

 

对于一幅无向图来说,只要2个顶点相连在同一路径上,那么这两个顶点就是连通的,从其中一个顶点可以到达另一顶点,求出哪些顶点是连通的也并不难。而对于有向图,受方向限制,两个连通的顶点并不一定能互相到达。如果两个连通的顶点可以相互到达,那么这两个顶点是强连通的。如果将图中所有的互相强连通的顶点划为一组,那么这组顶点就是强连通分量。高效求出强连通分量的算法有Kosaraju算法,Tarjan算法,Gabow算法。我表示我只能看懂Gabow算法。

据说Gabow算法和Tarjan算法的思想是一样的。每个强连通分量都有一个“根”,根是强连通分量中最先被检查的顶点。在一组强连通分量中,沿着根不断深度优先搜索,最终将会回到根,路上经过的顶点则属于这个强连通分量。

 

准备工作:

因为是演示,假定只有6个顶点,而且序号是连续的。#define MAXVERTEX 6

首先需要一个数组,用来保存当前顶点被检测的顺序。intOrder[MAXVERTEX];在这里将-1定为初始值,即没有被检测。Order[v]表示顶点v是第几个被检测的。除了这个数组,还需要一个变量,用来表示当前的顺序。intOrderNum=0;初始化为0表示还没开始检测。

结果也以数组的形式表示,为每个强连通分量分配一个不同的编号,而一个强连通分量中的所有顶点编号相同。int Part[MAXVERTEX];开始这个数组应该被初始化为-1,表示还不确定其中的顶点属于哪个强连通分量。同样还需要一个变量表示当前处理到第几个强连通分量。int PartNum=0;表示还没开始处理。

另外还需要2个辅助栈。第一个用来保存在一次深度优先搜索过程中遇到的顶点。stack<int> Path;这些顶点还没有被确定属于哪个强连通分量,在确定顶点属于某个强连通分量之后,就记录结果,将顶点弹出堆栈。第二个用来保存在一次深度优先搜索过程中遇到的根。stack<int>Root;

 

步骤1:

在所有顶点中,找一个没有被访问过的节点v,以v为参数执行步骤2。否则完成。

步骤2:

记录v的访问顺序。

将v压入堆栈Path和Root。

如果v指向其它的邻接顶点,那么对于每个邻接顶点next:

1) 如果没有访问过,则以next为参数,递归执行步骤2。

2) 如果访问过,而且没有确定它属于哪个强连通分量,弹出Root栈中next之后所有的顶点。

如果Root栈的顶元素等于v,那么在Part中记录顶点对应的的强连通分量。

最后递归返回。

 

代码:

#include <Windows.h>

#include <stack>

#include <vector>

using namespace std;

 

#define MAXVERTEX 6  //the number ofvertex

vector<int> Edge[MAXVERTEX]; //gragh

int Order[MAXVERTEX];//被检测顺序

int OrderNum=0;

int Part[MAXVERTEX]; // 连通变量的标号;

int PartNum=0;

stack<int> Path; //record dfs path;

stack<int> Root;//

 

void Gabow(int v)

{

    Order[v]=++OrderNum; //v  the order of visiting ;

    Path.push(v);

    Root.push(v);

 

    for (inte=0;e<Edge[v].size();e++)

    {

        int next=Edge[v][e]; //neighbor

        if (Order[next]==-1) //whether to visited

            Gabow(next);

        else if (Part[next]==-1)//visited and not label the connectivity;

        {

            while(Order[Root.top()]>Order[next])

                Root.pop();

        }

    }

 

    if (v==Root.top())

    {

        Root.pop();

        PartNum++;

        int Top;

        do

        {

            Top=Path.top();

            Part[Top]=PartNum;

            Path.pop();

        } while (Top!=v);

    }

}

 

int _stdcall WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTRlpCmdLine,int nShowCmd)

{

    Edge[0].push_back(1);Edge[0].push_back(2);

    Edge[1].push_back(3);

    Edge[2].push_back(3);Edge[2].push_back(4);

    Edge[3].push_back(0);Edge[3].push_back(5);

    Edge[4].push_back(5);

    //-1的十六进制表示为0xFFFFFFFF,所以memset没错

    memset(Order,-1,sizeof(Order));

    memset(Part,-1,sizeof(Part));

 

    for (int v=0;v<MAXVERTEX;v++)

        if (Order[v]==-1)

            Gabow(v);

 

    WCHAR Tmp[100]={0};

    WCHAR Result[200]={0};

    for (int i=1;i<PartNum+1;i++)

    {

        wsprintf(Tmp,TEXT("第%d组强连通分量:"),i);

        wcscat(Result,Tmp);

        for (intv=0;v<MAXVERTEX;v++)

        {

            if (Part[v]==i)

            {

                wsprintf(Tmp,TEXT("%d"),v);

                wcscat(Result,Tmp);

            }

        }

        wcscat(Result,TEXT("\n"));

    }

    MessageBox(NULL,Result,TEXT("Title"),MB_OK);

    return 0;

}

 

 

vector型数组Edge表示图的邻接表形式。图是这个样子的:

0 → 1 

↓↖ ↓

2 → 3 

↓   ↓

4 → 5 

 

WinMain函数中的循环逐个检查未被检查过的顶点,调用Gabow函数。

Gabow函数检测一个顶点v,将顶点v标记,并分别放入栈Path和Root中。

如果顶点v指向其它顶点,则对于每个指向的顶点next:

如果next是没有检测过的顶点(Order[next]不为-1),递归地调用Gabow函数,继续进行深度优先搜索检测。

如果next已经检测过,而且已经确定它所属的强连通分量(Part[next]!=-1),说明对它的检测已完成,且只能由v到next,不能由next到v。它现在不在栈中,忽略。

如果next已经检测过,但没有确定其所属的强连通分量(Part[next]==-1),说明现在发现了一个有向环。由next出发,到vi,到vj,到vk……到v,又到next。这些顶点都是强连通的。其中next是这个强连通分量的根,它最早进入Root栈,现在还在栈中。根据保存的顶点检查次序,将Root栈中next之后进入的顶点全部删除,仅保留根next。

其实每一次将顶点放入Root,都可以理解为每个检查到的顶点是一个单独的强连通分量,根就是它自己。直到发现下一个顶点在不久前检查过,确定了从那个顶点到这个顶点之间是一个环,才把这些顶点串了起来,此时最早检查的那个顶点就是它们的根。

对v所有指向的next进行同样的步骤继续检测,直到一个顶点没有邻接顶点,或一个顶点指向的所有顶点都已被检测。

现在已经能确定Path中的顶点所属的强连通分量了。获得Root最顶部的根顶点,这是最近的根顶点。Path中栈顶到这个根顶点之间的顶点为一个强连通分量。对比当前顶点v是否是这个根顶点,如果不是,直接返回,回到上一个检查的顶点进行对比。如果是,就删除Root顶部的根顶点,将强连通分量编号PartNum+1,同时将Path中栈顶到这个根顶点之间的顶点不断弹出,记录进Part数组中。

在递归的返回途中,如果发现某个顶点的下一个顶点没有被检测过,还会继续DFS,执行相同的步骤。不过,这些顶点就不一定与这个顶点强连通了。

这一系列深度优先搜索的递归完成之后,会检测图中的下一个顶点,如果下一顶点未被检测,将以同样的方式处理这个顶点。

 

流程图:

0 → 1 

↓↖ ↓

2 → 3 

↓   ↓

4 → 5 

Order

1

-1

-1

-1

-1

-1

Part

-1

-1

-1

-1

-1

-1

Path

0

 

 

 

 

 

Root

0

 

 

 

 

 

 

01 

↓↖ ↓

2 → 3 

↓   ↓

4 → 5 

Order

1

2

-1

-1

-1

-1

Part

-1

-1

-1

-1

-1

-1

Path

0

1

 

 

 

 

Root

0

1

 

 

 

 

 

01 

↓↖ ↓

2 → 3 

↓   ↓

4 → 5 

Order

1

2

-1

3

-1

-1

Part

-1

-1

-1

-1

-1

-1

Path

0

1

3

 

 

 

Root

0

1

3

 

 

 

 

01 

2 → 3 

↓   ↓

4 → 5 

Order

1

2

-1

3

-1

-1

Part

-1

-1

-1

-1

-1

-1

Path

0

1

3

 

 

 

Root

0

 

 

 

 

 

 

01 

↓↖ ↓

2 → 3 

↓   ↓

4 → 5 

Order

1

2

-1

3

-1

4

Part

-1

-1

-1

-1

-1

-1

Path

0

1

3

5

 

 

Root

0

5

 

 

 

 

 

01 

↓↖ ↓

2 → 3 

↓   ↓

4 → 5 

Order

1

2

-1

3

-1

4

Part

-1

-1

-1

-1

-1

1

Path

0

1

3

 

 

 

Root

0

 

 

 

 

 

 

01 

↓↖ ↓

23 

↓   ↓

4 → 5 

Order

1

2

5

3

-1

4

Part

-1

-1

-1

-1

-1

1

Path

0

1

3

2

 

 

Root

0

2

 

 

 

 

 

01 

↓↖ ↓

2 3 

↓   ↓

4 → 5 

Order

1

2

5

3

-1

4

Part

-1

-1

-1

-1

-1

1

Path

0

1

3

2

 

 

Root

0

 

 

 

 

 

 

01 

↓↖ ↓

23 

↓   ↓

45 

Order

1

2

5

3

6

4

Part

-1

-1

-1

-1

-1

1

Path

0

1

3

2

4

 

Root

0

4

 

 

 

 

 

01 

↓↖ ↓

23 

↓   ↓

4 5 

Order

1

2

5

3

6

4

Part

-1

-1

-1

-1

-1

1

Path

0

1

3

2

4

 

Root

0

4

 

 

 

 

 

01 

↓↖ ↓

23 

↓   ↓

45 

Order

1

2

5

3

6

4

Part

-1

-1

-1

-1

2

1

Path

0

1

3

2

 

 

Root

0

 

 

 

 

 

 

01 

↓↖ ↓

23 

↓   ↓

45 

Order

1

2

5

3

6

4

Part

3

3

3

3

2

1

Path

 

 

 

 

 

 

Root

 

 

 

 

 

 

 

Written By Wolfspirit(1152401936)

2013年4月25日6:30

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值