tarjan算法應用 割點 橋 雙連通分量


tarjan算法的應用。
還需多練習…….遇上題目還是容易傻住
對於tarjan算法中使用到的Dfn和Low數組.
low[u]:=min(low[u],dfn[v])——(u,v)為后向邊,v不是u的子樹;
low[u]:=min(low[u],low[v])——(u,v)為樹枝邊,v為u的子樹;
1.求割點:
割點:若刪掉某點后,原連通圖分裂為多個子圖,則稱該點為割點。
原理:若low[v]>=dfn[u],則u為割點。因low[v]>=dfn[u],則說明v通過子孫無法到達u的祖先。那么對於原圖,去掉u后,必然會分成兩個子圖。
所以處理節點u時,先遞歸v的子節點,然后回溯至u時,如果滿足low[v]>=dfn[u],則u為割點。

int tarjan(int x)
{
    v[x]=1;               //點的狀態標記,1為已訪問,2為割點 
    Dfn[x]=Low[x]=time++;
    for(int i=head[x];i;i=next[i])
    {
        if(!v[ver[i]])
        {
            tarjan(ver[i]);
            Low[x]=min(Low[x],Low[ver[i]]);
            if(Dfn[x]<=low[ver[i]])
            v[x]++;
        }
        else
        Low[x]=min(low[x],Dfn[ver[i]]);
        if((x==1&&v[x]>2)||(x>1&&v[x]>1))   //對第一個特判 
        v[x]=2;
        else
        v[x]=1;
    } 
} 

2.求橋
橋(割邊):刪掉它之后,圖必然會分裂為兩個或兩個以上的子圖。
原理:若low[v]>dfn[u],則(u,v)為橋。由割點同理可得。但是由於可能存在重邊,需要把一條無向邊拆成的兩條標號相同的有向邊,記錄每個點的父親到它的邊的標號,如果邊(u,v)是v的父親邊,就不能用dfn[u]更新low[v]。這樣如果遍歷完v的所有子節點后,發現low[v]=dfn[v],說明u的父親邊(u,v)為割邊。

void tarjan(int x)
{
 v[x]=1;
 Dfn[x]=Low[x]=time++;
 for(int i=head[x];i;i=next[i])
 {
     if(!v[ver[i]])
     {
      p[ver[i]]=edge[i];     //記錄父親邊
      tarjan(ver[i]);
      Low[x]=min(Low[x],Low[ver[i]]);
     }
     else if(p[x]!=edge[i])  //不是父親邊則更新
     Low[x]=min(Low[x],Dfn[ver[i]]);
     if(p[x]&&low[x]==dfn[x]) 
     f[p[x]]=1;             //為割邊
  }
 }

3.點雙連通分量
點雙連通分支,在求割點的過程中就能把每個點雙連通分支求出。建立一個棧,存儲雙連通分支。在搜索圖時,每找到一條樹枝邊或后向邊(非橫叉邊),就把這條邊加入棧中。如果遇到某時滿足DFS(u)<=Low(v),說明u是一個割點,同時把邊從棧頂一個個取出,直到遇到了邊(u,v),取出的這些邊與其關聯的點,組成一個點雙連通分支。

 if(dfn[u]==low[u])
    {
        scc++;
        while(1) //記錄每一個點屬於的連通塊
        {
            v=sta[top--];
            instack[v]=0;
            belong[v]=scc;  //所取出的點即為雙連通分支
            if(v==u)
                break;
        }
    }
}

4.邊雙連通分支。在求出所有的橋以后,把橋邊刪除,原圖變成了多個連通塊,則每個連通塊就是一個邊雙連通分支。橋不屬於任何一個邊雙連通分支,其余的邊和每個頂點都屬於且只屬於一個邊雙連通分支。
5.一個有橋的連通圖,如何把它通過加邊變成邊雙連通圖
首先求出所有的橋,然后刪除這些橋邊,剩下的每個連通塊都是一個雙連通子圖。把每個雙連通子圖收縮為一個頂點,再把橋邊加回來,最后的這個圖一定是一棵樹,邊連通度為1。
統計出樹中度為1的節點的個數,即為葉節點的個數,記為leaf。則至少在樹上添加(leaf+1)/2條邊,就能使樹達到邊二連通,所以至少添加的邊數就是(leaf+1)/2。

void Tarjan(int u,int fa)
{
    int i,v;
    low[u]=dfn[u]=++cnt;
    sta[++top]=u;
    instack[u]=1;
    for(i=first[u];i!=-1;i=edge[i].next)
    {
        v=edge[i].v;
        if(i==(fa^1))
            continue;
        if(!dfn[v])
        {
            Tarjan(v,i);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        scc++;
        while(1) //記錄每一個點屬於的連通塊
        {
            v=sta[top--];
            instack[v]=0;
            belong[v]=scc;
            if(v==u)
                break;
        }
    }
}
for(i=1;i<=n;i++)
        {
            for(j=first[i];j!=-1;j=edge[j].next)
            {
                v=edge[j].v;
                if(belong[i]!=belong[v])
                    degree[belong[i]]++;  //degree為1則為leaf
            }
        }
        int sum=0;
        for(i=1;i<=n;i++)
            if(degree[i]==1)
                sum++;    統計leaf數
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM