图的强连通&双连通
http://www.cnblogs.com/wenruo/p/4989425.html
强连通
强连通是指一个有向图中任意两点v1、v2间存在v1到v2的路径及v2到v1的路径。
dfs遍历一个图,会生成一颗树。每个节点按最先遍历的时间给定一个编号,用一个数组dfn表示,又叫时间戳。
然后有几个概念。
画图举例:
假设一个边是u-->v
树边:dfs遍历后生成树的边叫做树边。dfn[u] = -1 如图中<1,2> <2,3> <3,4> <2,5> <1,6> <6,7> <7,8>
前向边:dfn[u]<dfn[v] 如图中<1,4>
后向边:dfn[u]>dfn[v] 如图中<4,2>
前向边和后向边的两点公共最先为其中一点,即u或v中一点。
横跨边:dfn[u]>dfn[v] 如图中<6,5>
定义一个数组low用来记录一个结点通过任意条树边和最多一条横向边或向后边,所能到达的最小dfn值。
当一个结点low[n] == dfn[n] n就是一个强连通的根,即n的子树是一个强连通分量。可知一个强连通分量的dfn值都是连续的。
可以用一个根唯一的表示一个强连通分量。
强连通模板:
//强连通模板(tarjan) (hdu 1269 const int N = 10005; const int M = 100005; struct Edge { int to, next; } edge[M]; int head[N]; int cnt_edge; void add_edge(int u, int v) { edge[cnt_edge].to = v; edge[cnt_edge].next = head[u]; head[u] = cnt_edge; cnt_edge++; } int dfn[N];int idx; int low[N]; int stk[N];int top; int kind[N];int cnt; bool in[N]; int n, m; void dfs(int u) { dfn[u] = low[u] = ++idx; in[u] = true; stk[++top] = u; for (int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].to; if (!dfn[v]) { dfs(v); low[u] = min(low[v], low[u]); } else if(in[v]) low[u] = min(low[u], dfn[v]); } if (low[u] == dfn[u]) { ++cnt; int j; do { j = stk[top--]; in[j] = false; kind[j] = cnt; } while (j != u); } } void init() { memset(dfn, 0, sizeof dfn); memset(head, -1, sizeof head); cnt_edge = 0; top = cnt = idx = 0; } int main() { while (~scanf("%d%d", &n, &m)) { if (n == 0 && m == 0) break; int a, b; init(); for (int i = 0; i < m; ++i) { scanf("%d%d", &a, &b); add_edge(a, b); } for (int i = 1; i <= n; ++i) { if (!dfn[i]) dfs(i); } if (cnt == 1) puts("Yes"); else puts("No"); } return 0; }
双连通
定义:在无向连通图中,如果删除该图的任何一个结点都不能改变该图的连通性,则该图为双连通的无向图。
个人理解就是一个双连通图没有割点,没有桥的图。
在无向图中是没有前向边和横跨边的,只有树边和后向边。
如何找到割点和桥呢?
首先对于树根,如果他有多于两个的子结点,该根结点即为割点。
对于非根节点,画图举例:
low[v]<dfn[u] low[v]==dfn[u]
虚线连接到的位置就是low[v],观察可得当low[v]<=dfn[u]时,一旦去掉u点,f和v不再连通。所以当u不是树根时,任意一个子节点v满足low[v]>=dfn[u],u就是割点。
同时,当low[v]>dfn[u],(u,v)就是桥。
边的双连通分量比较简单, poj1438 & poj3177
点的双连通分量, poj2942 & hdu3394