RobertTarjanは本当に伝説級の大物です。
LCT彼の発明、スプレー木これらのデータ構造は本当に私に多くの利便性をもたらし、ダイナミックマップのさまざまなトピックは、LCTを解決するために使用することができます。
また、Tarjanは本当にLCT、コンピュータ科学への貢献の多くを発明していません。
この1は私が橋のとの視点を切断することなく得られ、彼の名前TarjanのアルゴリズムはO(n)に来ます。
さらに無向グラフは、DCC(二重連結成分)を得ることができます。無向グラフより、Tarjanアルゴリズムはまた、SCC図(強連結成分)を得ることができます。
TarjanのアルゴリズムDFSは、我々はいくつかの基本的な概念を導入しました。
DFN:タイムスタンプ
当社マップの深さ優先探索、それはマークをマーク与えるその年代順に訪問し、マークがセクションの下にタイムスタンプです。
検索ツリー:
深さ優先トラバーサル図任意に選択されたノードは一度だけ、各アクセスポイントを無向。すべては、再帰的な側面は、ツリーを構成するであろう、私たちはの無向連結グラフ呼んで発生した「探索木を。」
遡及値:
低い「非難値」:タイムスタンプに加えて、Tarjanのアルゴリズムはまた別の概念を導入しています。
我々は、最小の低[X]は、次のノードのサブツリー(X)とタイムスタンプとして定義され、サブツリーXのルートにツリーを検索します。
1.サブツリー(X)は、ノードのサブツリー(X)に到達することができるツリーエッジを介して検索・ノード2ではありません
のは、それを理解するための図を描いてみましょう:(、数字のノード番号は、そのタイムスタンプ便利です)
この絵の赤い側は、探索木であります
だから我々は、描画する傾向がある:サブツリーは、(2)= {4,3}、5は探索木に(2)側を通っていないのサブツリーに到達することができます。
したがって、低[2] =分、低得{[3]、[5] DFN DFN、[4] DFN} [2] = 3。
非常に明白な定義により、低[X]を算出する方法。サブツリー(x)は、Xを含むためシリング非常に低い[X] =のDFN [X]。
次いで、xから始まる各一の側(X、Y)を介して、低[X]を算出します。
いいえブリッジ図の点を切断する次の決定規則を与えられていません。
無向エッジ(x、y)があれば、ブリッジであり、xは低い探索木Yを満たすの子ノードである場合にのみ、[Y]> [X] DFN。
若x不是搜索树的根节点,则x是割点当且仅当搜索树上的一个子节点y满足low[y]>=dfn[x]。
若x是根节点,则x是割点当且仅当搜索树上存在至少两个x的子节点y1,y2满足上式。
桥边有以下性质:
1. 桥一定是搜索树中的边 2. 一个简单环中边都不是桥边
一个环被称为简单环当且仅当其包含的所有点都只在这个环中被经过了一次。
扩展内容:这里给出用dfs求出一个图中所有简单环的代码
int cnt; void dfs(int u){ dfn[u]=++cnt; for(int i=head[u];i;i=nxt(i)){ int v=to(i); if(v==fa[u])continue; if(!dfn[v])fa[v]=u,dfs(v); else if(dfn[u]<dfn[v]){ printf("%d",v); do{ printf(" %d",fa[v]);v=fa[v]; }while(v!=u); //找完一个环了 putchar('\n'); } } }
这个作为扩展内容就不再展开叙述。
下面给出求出无向图中所有的桥的代码:
#include<bits/stdc++.h> #define N 100010 using namespace std; inline int read(){ int data=0,w=1;char ch=0; while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar(); if(ch=='-')w=-1,ch=getchar(); while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar(); return data*w; } struct Edge{ int nxt,to; #define nxt(x) e[x].nxt #define to(x) e[x].to }e[N<<1]; int dfn[N],low[N],tot=1;//储存边的编号,由于要用^1找反向边,从1开始 int bridge[N],head[N]; int n,m,cnt; void addedge(int f,int t){ nxt(++tot)=head[f];to(tot)=t;head[f]=tot; } void tarjan(int x,int in_edge){//in_edge表示递归进入每个节点的边的编号 dfn[x]=low[x]=++cnt; for(int i=head[x];i;i=nxt(i)){ int y=to(i); if(!dfn[y]){ tarjan(y,i); low[x]=min(low[x],low[y]);//在搜索树上的边 if(low[y]>dfn[x])//桥判定法则 bridge[i]=bridge[i^1]=1;//这条边和它的反向边都是桥 }else if(i!=(in_edge^1)) low[x]=min(low[x],dfn[y]);//不在搜索树上的边 } } int main(){ n=read();m=read(); for(int i=1;i<=m;i++){ int x=read(),y=read(); addedge(x,y);addedge(y,x); } for(int i=1;i<=n;i++) if(!dfn[i])tarjan(i,0); for(int i=2;i<tot;i+=2) if(bridge[i]) printf("%d %d\n",to(i^1),to(i));//输出桥两边的点 }
以上就是求无向图中所有桥的程序了,可以自己画图模拟一下tarjan算法的流程加深理解。
下面给出求无向图中所有割点的程序:
这里需要注意的是,由于割点判定法则是小于等于号,所以不需要考虑父节点和重边的问题,所有dfn[x]都可以用来更新low[x]
#include<bits/stdc++.h> #define N 100010 using namespace std; inline int read(){ int data=0,w=1;char ch=0; while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar(); if(ch=='-')w=-1,ch=getchar(); while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar(); return data*w; } struct Edge{ int nxt,to; #define nxt(x) e[x].nxt #define to(x) e[x].to }e[N<<1]; int head[N],dfn[N],low[N],rt,tot=1,n,m,cnt; int cut[N]; inline void addedge(int f,int t){ nxt(++tot)=head[f];to(tot)=t;head[f]=tot; } void tarjan(int x){ dfn[x]=low[x]=++cnt; int flag=0; for(int i=head[x];i;i=nxt(i)){ int y=to(i); if(!dfn[y]){ tarjan(y); low[x]=min(low[x],low[y]); if(low[y]>=dfn[x]){//就一个割点判断法则没必要解释什么了吧? flag++; if(x!=rt||flag>1)cut[x]=1; } }else low[x]=min(low[x],dfn[y]); } } int main(){ n=read();m=read(); for(int i=1;i<=m;i++){ int x=read(),y=read(); if(x==y)continue; //自环直接判掉好吧,不多bb addedge(x,y);addedge(y,x); } for(int i=1;i<=n;i++) if(!dfn[i])rt=i,tarjan(i);//无向图不一定连通,对每一个连通块都要跑一发tarjan for(int i=1;i<=n;i++) if(cut[i])printf("%d ",i); return 0; }
桥边判定法则和割点判定法则后面会update上,这一篇暂时更到这里,下一篇讲e-DCC和v-DCC的求法
主要是联赛在即,更新尽量多的算法扎实自己基础才要紧些...请多见谅