无向图找桥(参考的一个acm模板,不知道挂啥链接)
算法思想:
- 设置vis, pre, anc数组。
- pre[cur]:cur结点dfs时访问时的时间’。
- anc[cur]: 记录cur结点及其子孙结点最早祖先的dfs时间(朔源到尽头)。
- vis标记结点访问状态:0:未访问, 1:正在访问, 2:访问结束。
- 访问当前结点cur时,通过其子孙递归的更新其最早祖先的dfs时间,找出所有桥。dfs过程中,其孩子节点child如果还未开始访问则child为树边,递归;若已经开始访问且未结束,且不是cur父亲,则此节点为cur的祖先,可以更新anc[cur]。
- 对于cur -> child,anc[child] > pre[cur] 得出 child -> cur为桥。证明:anc[child] > pre[cur]表示child及其任意子孙,能溯源到的最早时间晚于cur结点,于是child及其子孙无法通过任何途径到达cur结点(除了child -> cur)。
题目链接
邻接矩阵版
/*==================================================*\
| 无向图找桥(删除桥后图不连通)
| INIT: edge[][]邻接矩阵;vis[],pre[],anc[],bridge 置0;
| CALL: dfs(0, -1, 1, n);
| Tarjan算法
\*==================================================*/
int edge[V][V], anc[V], pre[V], vis[V];
vector<vector<int> > ans;
void dfs(int cur, int father, int dep, int n){ // vertex: 0 ~ n-1
vis[cur] = 1; //开始访问
pre[cur] = anc[cur] = dep;
for(int i = 0; i < n; ++i)
if(edge[cur][i]){
if(i != father && 1 == vis[i]){ //i访问还未结束,所以为cur的祖先
if(pre[i] < anc[cur]) //更新cur的最早祖先
anc[cur] = pre[i]; //back edge
}
if(0 == vis[i]){ //tree edge,i还未访问,搜索树的树边
dfs(i, cur, dep+1, n);
if(anc[i] < anc[cur]) //如果cur的最小祖先大于i的最小祖先,且存在cur->i,则更新
anc[cur] = anc[i]; //cur的最小祖先
if(anc[i] > pre[cur]) //如果cur的搜索时间比child的最早的祖先时间还小则找到一条桥
ans.push_back({cur, child});
}
}
vis[cur] = 2; //结束访问
}
邻接表版
int anc[V], pre[V], vis[V];
vector<vector<int> > ans;
vector<int> adj[V];
void dfs(int cur, int father, int dep, int n){ // vertex: 0 ~ n-1
vis[cur] = 1; //开始访问
pre[cur] = anc[cur] = dep;
for(int i = 0; i < adj[cur].size(); ++i){
int child = adj[cur][i];
if(child != father && 1 == vis[child]){ //child访问还未结束,所以为cur的祖先
if(pre[child] < anc[cur]) //更新cur的最早祖先
anc[cur] = pre[child]; //back edge
}
if(0 == vis[child]){ //tree edge, child还未访问,搜索树的树边
dfs(child, cur, dep+1, n);
if(anc[child] < anc[cur]) //如果cur的最小祖先大于child的最小祖先,且存在cur->child,
anc[cur] = anc[child]; //则更新cur的最小祖先
if(anc[child] > pre[cur]) //如果cur的搜索时间比child的最早的祖先时间还小则找到一条桥
ans.push_back({cur, child});
}
}
vis[cur] = 2; //结束访问
}