tarjan算法不是很懂先mark一下。

9 篇文章 0 订阅
 前面为转载的。后面是自己的理解。

三种tarjan算法(上)

。这篇算是做一个总结吧。
  1. 求强连通分量
  2. 求无向图的割和桥
  3. 最近公共祖先


求强连通分量


 基本概念:

      强连通是有向图才有的概念。一个有向图是强连通的是对于每个有序对 u,v,存在一条从 u到v  的路径。一个有向图的强连通分量是指它的极大连通子图。就是你可以从 a地走到 b 地,同样可以从b地走到a地,a,b就是连通的。
      下图(图片来自这里)中 顶点 {1,2,3,4}是一个强连通分量,因为它们两两都可以到达。直观上来讲,所谓的强连通,(如果有至少有一个顶点)就是可以至少形成一个环。如1->3->4->1就是一个环,它们就是强连通的。注意也有像{5},{6}这样的的连通分量。





      强连通的tarjan 算法是基于图的深度优先搜索(这个经常用于获取图的各种信息)。下面说一下几个约定:  

  1. 时间戳       :DFN[i]是指结点i被遍历的时间。
  2. Low[i]       :是指在搜索树中,结点i和其子孙可以访问到的最早的祖先,Low[i] = Min(DFN[i], DFN[j], Low[k])其中j是i的祖先(我们把子孙连到祖先的边叫后向边),k是i 的子女。
  3. 结点的颜色color[i]是用于标示结点i的状态:白色指还没到搜索到,灰色正在被搜索,黑色处理完毕。在实际操作中用-1,0,1分别代表白色、灰色、黑色

      tarjan算法的步骤:     

  1. 先把所有的结点的颜色都初始化白色,并把栈清空。
  2. 找到一个白色的结点i(即结点的颜色为白色)
  3. 给结点一个时间戳,把结点圧入栈中,并把结点标记为灰色。令Low[i] = DFN[i] 
  4. 遍历结点i 的每条边(i,j)。若color[j]是白色,就对结点i重复2~5步骤。并令Low[i] = min(Low[j],low[i]).如果color[j]是灰色,令Low[i] = min(Low[i],DFN[j])。如果是黑色不做任何处理。
  5. 把结点的颜色改为黑色,如果Low[i] = DFN[i],就把从栈顶到结点i间的元素弹出
  6. 重复步骤2,至到没有白色顶点

      下面是算法的一个模板:

[cpp]  view plain  copy
  1. <pre name="code" class="cpp">#include <math.h>  
  2. #include <stdio.h>  
  3. #include <string.h>  
  4. #include <stdlib.h>  
  5.   
  6. //从顶点0开始  
  7. // 要用的话要初始化:调用Adj.initial 和 tarjan.initial  
  8. //要解决问题用调用tarjan.solve  
  9. //对tarjan.initial要传入的参数是图边集Adj,和顶点个数n  
  10.   
  11. const int maxn = 11000;  
  12. //顶点的规模  
  13. const int maxm = 210000;  
  14. //边的规模,如果是无向图要记得乘以2  
  15.   
  16. const int GRAY = 0;  
  17. const int WHITE =-1;  
  18. const int BLACK = 1;  
  19.   
  20. typedef struct Edge{  
  21.     int s;  
  22.     int e;  
  23.     int next;  
  24. }Edge;  
  25.   
  26. typedef struct Adj{  
  27.     int edge_sum;  
  28.     int head[maxn];  
  29.     Edge edge[maxm];  
  30.   
  31.     void initial(){   
  32.         edge_sum = 0;  
  33.         memset(head,-1,sizeof(head));  
  34.     }  
  35.   
  36.     void add_edge(int a, int b){  
  37.         edge[edge_sum].s = a;  
  38.         edge[edge_sum].e = b;  
  39.         edge[edge_sum].next = head[a];  
  40.         head[a] = edge_sum++;  
  41.     }  
  42. }Adj;  
  43.   
  44. typedef struct Tanjan{  
  45.     int n;  
  46.     int *head;  
  47.     Adj *adj;  
  48.     Edge *edge;  
  49.   
  50.     int cnt;  
  51.     int top;  
  52.     int cur;  
  53.   
  54.     int dfn[maxn];  
  55.     int low[maxn];  
  56.     int color[maxn];  
  57.     int stack[maxn];  
  58.     int belong[maxn];  
  59.   
  60.     void initial(Adj *_adj,int _n){  
  61.         n = _n;  
  62.         adj = _adj;  
  63.         head = (*adj).head;  
  64.         edge = (*adj).edge;  
  65.     }  
  66.   
  67.     void solve(){  
  68.         memset(dfn,-1,sizeof(dfn));  
  69.         memset(color,WHITE,sizeof(color));  
  70.   
  71.         top = cnt = cur = 0;  
  72.         for(int i = 0; i < n; i++)  
  73.             if(color[i] == WHITE)//找到一个白色的顶点,就开始处理  
  74.                 tarjan(i);  
  75.     }  
  76.   
  77.     inline int min(int a, int b){  
  78.         if(a < b) return a;  
  79.         else return b;  
  80.     }  
  81.   
  82.     void tarjan(int i){  
  83.         int j = head[i];  
  84.   
  85.         color[i] = GRAY;//标记为灰色  
  86.         stack[top++] = i;//把结点圧入栈顶  
  87.   
  88.         dfn[i] = low[i] = ++cur;//给结点一个时间戳,并给Low初始化  
  89.   
  90.         while(j != -1){  
  91.             int u = edge[j].e;  
  92.             if        (dfn[u] == WHITE){  
  93.                 tarjan(u);  
  94.                 low[i] = min(low[i],low[u]);  
  95.             //更新low   
  96.             }else  if (color[u] == GRAY)  
  97.                 low[i] = min(low[i],dfn[u]);  
  98.             //一条后向边  
  99.             j = edge[j].next;  
  100.         }  
  101.   
  102.         color[i] = BLACK;  
  103.         if(low[i] == dfn[i]){  
  104.             do{  
  105.                 j = stack[--top];  
  106.                 belong[j] = cnt;  
  107.             }while(i != j);  
  108.             ++cnt;    
  109.         }  
  110.     }  
  111. }Tarjan;  
  112.   
  113. Adj adj;  
  114. Tarjan tj;</pre><br>  
  115. <br>  
  116. <pre></pre>  
  117. <pre></pre>  
  118. <pre></pre>  
  119. <pre></pre>  
  120. <pre></pre>  

tarjan算法的简单证明:

         首先,这边再重复一下什么是后向边:就是在深度优先搜索中,子孙指向祖先的边。在一棵深度优先搜索树中,对于结点v, 和其父亲结点u而言,u,v 属于同一个强连通分支的充分必要条件是  以v为根的子树中,有一条后向边指向u或者u的祖先。

1 、必要性。  
           如果 u,v属于同一个强连通分支则必定存在一条 u到 v的路径和一条v到u的路径。合并两条则有 u->v->v1->v2->..vn->u, 若顶点v1到vn都是v 的子孙,则有 vn->u这样一条后向边。
          如果v1到vn 不全是vn的子孙,则必定有一个是u的祖先,我们不妨设vi为u的祖先,则有一条后向边 V[i-1] ->v[i]。

2.、充分性。    我们设 u1->u2->u3..->un->u->v->v1->v2..->vn,我们假设后向边vn指向ui则有这样一个环:u[i]->u[i+1]...->u->v->v1->v2..->v[n-1]->v[n]->u[i],易知,有一条u->v的路径,同时有v->u的路径。固u,v属于同一连通分支。

        在算法开始的时候,我们把i圧入栈中。 根据low[i] 和 dfn[i]的定义我们知道,
        如果low[i] < dfn[i] 则以i为顶点的子树中,有指向祖先的后向边,则说明i和i的父亲为在同一连通分支,也就是说 留在栈中的元素都是和父结点在同一连通分支的
         如果low[i] == dfn[i],则  i为顶点的子树中没有后向边,那么由于  留在 栈中的元素都是和父结点在同一连通分支的,我们可以知道,从栈顶到元素i构成了一个连通分支。显然,low[i]不可能小于dfn[i]。
__________________________________________________________________________________________________---- 我的理解,感觉有点类似于并查集,一开始假设有n个连通分量既dfn,low是我们进行判断后修正的既low[n]=m表示实际上n这点属于m这个连通分量,个点属于哪个连通分量。然后进行深搜当找到一个环既(Instack[j]==true)这次找到的点是我们之前搜索到的,那这个点就属于dfn[j]这个连通分量。
#define M 5010//题目中可能的最大点数
int STACK[M],top=0;//Tarjan算法中的栈
bool InStack[M];//检查是否在栈中
int DFN[M];//深度优先搜索访问次序
 
int Low[M];//能追溯到的最早的次序
int ComponentNumber=0;//有向图强连通分量个数
int Index=0;//索引号
vector<int> Edge[M];//邻接表表示
vector<int> Component[M];//获得强连通分量结果
int InComponent[M];//记录每个点在第几号强连通分量里
int ComponentDegree[M];//记录每个强连通分量的度
 
void Tarjan(int i)
{
    int j;
    DFN[i]=Low[i]=Index++;
    InStack[i]=true;STACK[++top]=i;
    for (int e=0;e<Edge[i].size();e++)
    {
        j=Edge[i][e];
        if (DFN[j]==-1)
        {
            Tarjan(j);
            Low[i]=min(Low[i],Low[j]);
        }
        else
            if (InStack[j]) Low[i]=min(Low[i],DFN[j]);
    }
    if (DFN[i]==Low[i])
    {
        ComponentNumber++;
        do{
            j=STACK[top--];
            InStack[j]=false;
            Component[ComponentNumber].
            push_back(j);
            InComponent[j]=ComponentNumber;
        }
        while (j!=i);
    }
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值