tarjan求关键连接java,Tarjan算法与割点割边(示例代码)

Tarjan算法与无向图的连通性

1:基础概念

在说Tarjan算法求解无向图的连通性之前,先来说几个概念:

<1. 时间戳:在图的深度优先遍历中,按照每一个结点第一次被访问到的时间顺序,依次给予N个结点1~N的整数边集,该标记就被计位“时间戳”,计做 (dfn[x])。

<2. 搜索树:任选一个结点深度优先遍历,每个点只访问一次。产生递归的边构成的树为搜索树。

<3. (subtree(x)):搜索树中以x为根的子树。

<4. 追溯值:追溯值((low[x]))定义为以下结点的时间戳的最小值,这些结点满足:

①:是(subtree(x))的结点; ②:通过一条不在搜索树上的边,能够到达(subtree(x))的结点

在了解概念之后,我们可以根据定义来计算(low[x]):

令(low[x]=dfn[x]);然后考虑每个从x的出边( x, y ),如果x是y的父节点,则(low[x]=min(low[x],low[y]));如果边不是搜索树上的边,则令(low[x]=min(lwo[x],dfn[y])); //ps:后面代码也会提到

2:Tarjan判断割点

判定定理:对于一个点 x ;如果x不为根节点,那么x是割点当且仅当搜索树上存在x 的一个子节点y,满足:

[

dfn[x]<=low[y]

]

如果x是根节点,那么搜索树上至少存在两个子节点满足上述性质。

代码模板(求割点个数,及从小到大输出):

#include

using namespace std;

const int maxn=1e5+10;

int head[maxn],n,m,tot;

struct Edge

{

int nex,to;

}edge[maxn<<1];

void add(int from,int to)

{

edge[++tot].to=to;

edge[tot].nex=head[from];

head[from]=tot;

}

int dfn[maxn],low[maxn],st[maxn],idx,root;

bool cut[maxn];

void tarjan(int u) //tarjan求一下dfn和low

{

dfn[u] = low[u] = ++idx; //初始化dfn和low

int flag=0;

for(int i=head[u];i!=-1;i=edge[i].nex)

{

int v=edge[i].to;

if(dfn[v]) low[u]=min(low[u],dfn[v]); //定义,追溯值为子树最小的时间戳

else{

tarjan(v); //找子节点的子节点

low[u]=min(low[u],low[v]);

if(dfn[u]<=low[v]){ //判定条件

flag++;

if(u!=root||flag>1) cut[u]=true;

}

}

}

}

int main()

{

scanf("%d %d",&n,&m);

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

tot=0;

for(int i=1;i<=m;++i){

int a,b;

scanf("%d %d",&a,&b);

if(a==b) continue;

add(a,b);

add(b,a);

}

for(int i=1;i<=n;++i){

if(!dfn[i]){

root=i;

tarjan(i);

}

}

int res=0; //个数

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

if(cut[i]) res++;

printf("%d

",res);

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

if(cut[i]) printf("%d ",i);

printf("

");

}

如果要求连通分量数,还要写个dfs即可

void dfs(int u)

{

vis[u]=1;

for(int i=head[u];i!=-1;i=edge[i].nex){

int v=edge[i].to;

if(vis[v]) continue;

vis[v]=1;

dfs(v);

}

}

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

{

if(cut[i])

{

int son=0;

memset(vis,0,sizeof(vis));

vis[i]=1;

for(int j=head[i];j!=-1;j=edge[j].nex){

int v=edge[j].to;

if(vis[v]) continue;

dfs(v);

son++;

}

printf("%d

",son); //son为连通分量数

}

}

3:Tarjan判断割边

割边判断法则:无向边(x,y)是桥当且仅当搜索树上存在 x 的一个子节点 y ,满足:

[

dfn[x]

]

与割点不同的是,这里不能取等号。这个不等式其实也很好理解,根据定义(dfn[x]

#include

using namespace std;

const int maxn=1e5+10;

struct Edge

{

int nex,to;

}edge[maxn<<1];

int n,m,tot,head[maxn];

void add(int from,int to) //这里边从下标为2开始存方便异或处理

{

edge[++tot].to=to;

edge[tot].nex=head[from];

head[from]=tot;

}

int bridge[maxn<<1],dfn[maxn],low[maxn],idx;

void tarjan(int u,int fa)

{

dfn[u]=low[u]=++idx;

for(int i=head[u];i!=-1;i=edge[i].nex)

{

int v=edge[i].to;

if(!dfn[v]){

tarjan(v,i);

low[u]=min(low[u],low[v]);

if(dfn[u]

}

else if(i!=(fa^1)) low[u]=min(low[u],dfn[v]);

}

}

int main()

{

scanf("%d %d",&n,&m);

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

tot=1; //注意边的下标从2开始储存

for(int i=1;i<=m;++i){

int a,b;

scanf("%d %d",&a,&b);

add(a,b);

add(b,a);

}

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

if(!dfn[i]) tarjan(i,-1);

for(int i=2;i<=tot;i+=2)

if(bridge[i]) printf("%d %d

",edge[i].to,edge[i^1].to);

system("pause");

}

【题意】:给了一幅图,问需要加几条边将这幅图所有的结点都在环上(可能在不同的环上)

求一下割边,判断一下叶结点的数量,答案即为(num+1)>>1;看代码:

#include

#include

#include

using namespace std;

const int maxn=1e4+10;

int head[maxn],tot;

struct Edge

{

int from,nex,to;

}edge[maxn<<1];

void add(int from,int to)

{

edge[++tot].to=to;

edge[tot].from=from;

edge[tot].nex=head[from];

head[from]=tot;

}

int dfn[maxn],low[maxn],idx;

bool bridge[maxn];

void tarjan(int u,int fa) //求割边(桥)

{

dfn[u]=low[u]=++idx;

for(int i=head[u];i!=-1;i=edge[i].nex)

{

int v=edge[i].to;

if(!dfn[v]){

tarjan(v,i);

low[u]=min(low[u],low[v]);

if(dfn[u]

bridge[i]=bridge[i^1]=true;

}

}

else if(i!=(fa^1)) low[u]=min(low[u],dfn[v]); //异或打括号!!

}

}

int n,m,outd[maxn],id[maxn],dcc; //id为双连通分量编号,outd为出度

void dfs(int u) //跑一遍dfs求一下各个边的双连通分量编号

{

id[u]=dcc;

for(int i=head[u];i!=-1;i=edge[i].nex)

{

int v=edge[i].to;

if(bridge[i]||id[v]) continue; //双连通分量不包括桥

dfs(v);

}

}

int main()

{

scanf("%d %d",&n,&m);

tot=1;

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

for(int i=1;i<=m;++i){

int a,b;

scanf("%d %d",&a,&b);

add(a,b);

add(b,a);

}

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

if(!dfn[i]) tarjan(i,-1);

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

if(!id[i]){

dcc++;

dfs(i);

}

for(int i=2;i<=tot;i+=2)

{

int u=edge[i].from,v=edge[i].to;

if(id[u]!=id[v]){

outd[id[u]]++;

outd[id[v]]++;

}

}

int ans=0; //叶子结点数量

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

if(outd[i]==1) ans++;

printf("%d

",(ans+1)>>1);

}

你可能感兴趣的:(tarjan求关键连接java)