终于把tarjan算法理解了
整个tarjan算法有两个非常重要的数组,一个是Low一个是DFN
DFN数组是个时间戳,说白了就是访问那个节点的时间,也就是第几次调用DFS这个函数
Low是u的子节点能通过反向边到达的节点DFN的最小值,初始值为DFN[u]
那么如何判断是否是割顶和割边呢
对于上面这个建图,假如我们最开始的根节点是1,然后会得到这样的数组
id 1 2 3 4 5 6 dfn 1 2 3 4 5 6 low 1 1 1 4 4 4
对于割顶,个人认为可以分成3种情况
第一种情况,是对我们DFS树的根节点,对于我刚刚说的,我们一开始访问的是1,那么 1就是根节点,
这个点是不是割顶呢?这要看它儿子的数量,如果>=2,那么把这个点拆了,必然会有增加的连通分量。如果等于1,那么拆了也不会有增加的连通分量,所以儿子数量等于1,就不是割顶,大于1就是割顶
第二种情况,对于某个点u,设其儿子为v,如果存在一个v,使得low[v] >dfn[u] ,在上面这个样例里面,有存在的情况,u=3,v=4就是满足这种情况的
第三种情况,对于某个点u,设其儿子为v,如果存在一个v,使得low[v]==dfn[u,在上面这个样例里面,有存在的情况,u=4,v=5就是满足这种情况的
对于桥,就是割顶的第二种情况
对于某个点u,设其儿子为v,如果存在一个v,使得low[v] >dfn[u] ,在上面这个样例里面,有存在的情况,u=3,v=4就是满足这种情况的,那么说明v怎么也回不到u的父节点去,所以此时u-v这条边就是桥
#include<cstdio> #include<cmath> #include<cstring> #include<queue> #include<vector> #include<functional> #include<algorithm> using namespace std; typedef long long LL; typedef pair<int, int> PII; const int MX = 2e5 + 5; const int INF = 0x3f3f3f3f; int rear; int Head[MX], Next[MX]; int Low[MX], DFN[MX], dfs_clock; int cut[MX]; struct Edge { int u, v, sign; } E[MX]; void edge_init() { rear = 0; memset(Head, -1, sizeof(Head)); memset(Next, -1, sizeof(Next)); } void edge_add(int u, int v) { E[rear].u = u; E[rear].v = v; E[rear].sign = false; Next[rear] = Head[u]; Head[u] = rear++; } int tarjan(int u, int from) { Low[u] = DFN[u] = ++dfs_clock; int child = 0; for(int id = Head[u]; ~id; id = Next[id]) { int v = E[id].v; if(!DFN[v]) { int lowv = tarjan(v, u); Low[u] = min(Low[u], lowv); if(lowv >= DFN[u]) { cut[u] = 1; } if(lowv > DFN[u]) { E[id].sign = 1; E[id ^ 1].sign = 1; } child++; } else if(v != from) { Low[u] = min(Low[u], DFN[v]); } } if(from == -1 && child == 1) cut[u] = 0; return Low[u]; } int main() { int n, m; scanf("%d%d", &n, &m); dfs_clock = 0; edge_init(); memset(DFN, 0, sizeof(DFN)); memset(cut, 0, sizeof(cut)); for(int i = 1; i <= m; i++) { int u, v; scanf("%d%d", &u, &v); edge_add(u, v); edge_add(v, u); } tarjan(1, -1);//如果已经确定是连通图,就这样写 /* 否则要这样 for(int i = 1; i <= n; i++) { if(!DFN[i]) tarjan(1, -1); } */ int cnt = 0, first = true; for(int i = 1; i <= n; i++) { if(cut[i]) { cnt++; if(first) first = false; else printf(" "); printf("%d", i); } } printf("%s\n", cnt ? "" : "Null"); vector<PII>ans; for(int i = 0; i < rear; i++) { if(E[i].sign && E[i].u < E[i].v) { ans.push_back(PII(E[i].u, E[i].v)); } } sort(ans.begin(), ans.end()); for(int i = 0; i < ans.size(); i++) { printf("%d %d\n", ans[i].first, ans[i].second); } return 0; }