寻找无向连通图的割点
无向连通图的割点又被成为关节点(articulation point), 移除关节点,则原连通图变成两个连通分量, 一个没有关节点的连通图被成为重连通图 (biconnected graph)
要寻找无向连通图中所有的关节点,比较便捷的方法是利用一次深度优先遍历,找到所有关节点
对于节点u,及其子节点v, u->v, 可以用数组dfn[u]来表示当前u的遍历序列号,用low[u]表示u及u的子节点通过非tree edge所能找到的最早的祖先节点的序列号, 如果u是关节点,则有两种情况:
1. 如果u是根节点,且u有两棵及以上的子树,则节点u为关节点,很明显,移除u会使其两棵子树变成不连通的两个连通分量
2. 如果对于节点v, low[v]的值 >= dfn[u], 说明v及其子节点最多能指到节点u, 没有其他路径能指到u的祖先,则移除u,其子节点v会成为独立的连通分量
得到公式: low[u] = min (
dfn[u],
low[v], u -> v 是tree edge, 即节点v以前未被dfs遍历过, 此时low[u] = min(low[u], low[v])
dfn[v], u -> v 是back edge, 即节点v已经被访问过, 此时low[u] = min(low[u], dfn[v]),
为什么不是low[v]而是dfn[v], 因为此时节点v已经被访问过了,说明 u->v是条回边,v是当前节点u的祖先,我们直接取该祖先的遍历序列值即可
)
下表给出图(a)对应的dfn与low数组值。
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
vertex | A | B | C | D | E | F | G | H | I | J | K | L | M |
dfn[i] | 1 | 5 | 12 | 10 | 11 | 13 | 8 | 6 | 9 | 4 | 7 | 2 | 3 |
low[i] | 1 | 1 | 1 | 5 | 5 | 1 | 5 | 5 | 8 | 2 | 5 | 1 | 1 |
1 package com.rui.microsoft; 2 3 public class Test39_FindCutPoints { 4 5 public static void main(String[] args) { 6 UndirectedGraph dGraph = new UndirectedGraph(5); 7 dGraph.addEdge(0, 1); 8 dGraph.addEdge(1, 2); 9 dGraph.addEdge(2, 0); 10 dGraph.addEdge(0, 3); 11 dGraph.addEdge(3, 4); 12 dGraph.tarjan(0); 13 14 for(int i = 0; i < dGraph.V; i++){ 15 if(dGraph.APs[i]){ 16 System.out.println(" " + i); 17 } 18 } 19 } 20 } 21 22 class UndirectedGraph{ 23 24 int V; 25 Bag<Integer>[] adj; 26 27 boolean[] visited; 28 int[] dfn; 29 int[] low; 30 int[] parents; 31 32 boolean[] APs; 33 34 int order = 0; 35 36 public UndirectedGraph(int size) { 37 this.V = size; 38 this.adj = new Bag[size]; 39 40 this.dfn = new int[size]; 41 this.low = new int[size]; 42 this.parents = new int[size]; 43 this.visited = new boolean[size]; 44 this.APs = new boolean[size]; 45 46 for(int i = 0; i < size; i++){ 47 this.adj[i] = new Bag<Integer>(); 48 this.parents[i] = -1; 49 this.APs[i] = false; 50 } 51 } 52 53 void addEdge(int from, int to){ 54 adj[from].add(to); 55 adj[to].add(from); 56 } 57 58 void tarjan(int u){ 59 dfn[u] = low[u] = ++order; 60 visited[u] = true; 61 int childs = 0; 62 63 //dfs 64 for(int v: this.adj[u]){ 65 if(!visited[v]){ 66 childs++; 67 parents[v] = u; 68 tarjan(v); 69 70 //after dfs the subtree of v 71 low[u] = min(low[u], low[v]); 72 73 //check 74 if(parents[u] == -1 && childs > 1){ 75 APs[u] = true; 76 } 77 78 if(parents[u] != -1 && dfn[u] <= low[v]){ 79 APs[u] = true; 80 } 81 }else{ 82 //back edge 83 low[u] = min(low[u], dfn[v]); 84 } 85 } 86 } 87 88 int min(int a, int b){ 89 return a<b? a: b; 90 } 91 }
辅助类Bag
1 package com.rui.microsoft; 2 3 import java.util.Iterator; 4 import java.util.NoSuchElementException; 5 6 public class Bag<Item> implements Iterable<Item>{ 7 8 private Node<Item> first; 9 private int N; 10 11 private static class Node<Item>{ 12 private Item item; 13 private Node<Item> next; 14 } 15 16 public Bag(){ 17 first = null; 18 N = 0; 19 } 20 21 public boolean isEmpty(){ 22 return first == null; 23 } 24 25 public int size(){ 26 return N; 27 } 28 29 public void add(Item item){ 30 Node<Item> oldFirst = first; 31 first = new Node<Item>(); 32 first.item = item; 33 first.next = oldFirst; 34 N++; 35 } 36 37 @Override 38 public Iterator<Item> iterator() { 39 return new ListIterator<Item>(first); 40 } 41 42 private class ListIterator<Item> implements Iterator<Item> { 43 private Node<Item> current; 44 45 public ListIterator(Node<Item> first) { 46 current = first; 47 } 48 49 public boolean hasNext() { return current != null; } 50 public void remove() { throw new UnsupportedOperationException(); } 51 52 public Item next() { 53 if (!hasNext()) throw new NoSuchElementException(); 54 Item item = current.item; 55 current = current.next; 56 return item; 57 } 58 } 59 60 }