(譯)割點
註:本文翻譯自http://www.geeksforgeeks.org/articulation-points-or-cut-vertices-in-a-graph/。如有翻譯錯誤請指正。
一個無向聯通圖(undirected connected graph)中的頂點,如果去掉它會使圖不再聯通,就是割點(articulation point/cut vertex)。割點表示一個連通網絡的脆弱性——一個點出問題會將整個網絡分成2個或更多個部分。在設計可靠的網絡中,它們很有用。
對於一個無向聯通圖,一個割點是一個去掉之後會增加連通分量(connected component)數量的點。
下面是一些例子,割點用紅色圓圈圈出:
怎麽找到一個圖裏的全部割點?
一個簡單方法就是一個接一個地去掉所有頂點,然後看去掉該點是否導致圖不再連通。下面是這種簡單方法的步驟:
對於每個頂點v
a) 將v從圖中去掉
b) 看圖是否仍是連通的(既可以使用BFS,也可以使用DFS)
c) 將v重新加入圖
上述方法的時間復雜度為O(V*(V+E)),如果使用鄰接表存儲圖。可以更好嗎?
尋找所有割點的O(V+E)算法
方法是使用DFS(depth first search,深度優先搜索)。在DFS中,我們用樹的形式跟蹤頂點,叫做DFS樹。在DFS樹中,一個頂點u是另一個頂點v的父節點(parent),如果v已經被u發現(很明顯v在圖上與u相鄰)。在DFS樹中,如果以下兩個條件之一成立,則u是割點:
1) u是DFS樹的根節點,並且有兩個以上的子節點。
2) u不是DFS樹的根節點,它有一個子節點v,並且以v為根節點的子樹中,沒有一個頂點有通向任意一個u的祖先的回邊。
下面的圖片顯示了與上面一樣的概念,還有一點補充,就是DFS樹中的葉節點不可能是割點。
對於給定的圖,我們用DFS遍歷和附加代碼,尋找割點(articulation points,APs)。在DFS遍歷中,我們維護一個parent[]數組,parent[u]存儲u的父節點。在上面提及的兩種情況中,第一種情況很好檢測。對於每個頂點,數出其子節點數量。如果目前訪問過的頂點u是根節點(parent[u]為NIL),並且有兩個以上的子節點,輸出。
第二種情況怎麽處理?第二種情況更復雜。我們維護一個disc[]數組,存儲一個頂點被發現的時刻(譯者註:即從根節點開始DFS,第幾次才能到達該頂點)。對於每個節點u,我們需要找到最早訪問過的頂點(訪問時刻最低的頂點),且該頂點可以從以u為根節點的子樹(的某個頂點)到達。因此我們維護一個額外的數組low[],定義如下。
low[u] = min(disc[u], disc[w])
w為u的祖先,並且存在一條回邊,可以從u的後代到達w。
下面是用來尋找割點的Tarjan算法的C++、Java和Python實現。
C++
// A C++ program to find articulation points in an undirected graph #include<iostream> #include <list> #define NIL -1 using namespace std; // A class that represents an undirected graph class Graph { int V; // No. of vertices list<int> *adj; // A dynamic array of adjacency lists void APUtil(int v, bool visited[], int disc[], int low[], int parent[], bool ap[]); public: Graph(int V); // Constructor void addEdge(int v, int w); // function to add an edge to graph void AP(); // prints articulation points }; Graph::Graph(int V) { this->V = V; adj = new list<int>[V]; } void Graph::addEdge(int v, int w) { adj[v].push_back(w); adj[w].push_back(v); // Note: the graph is undirected } // A recursive function that find articulation points using DFS traversal // u --> The vertex to be visited next // visited[] --> keeps tract of visited vertices // disc[] --> Stores discovery times of visited vertices // parent[] --> Stores parent vertices in DFS tree // ap[] --> Store articulation points void Graph::APUtil(int u, bool visited[], int disc[], int low[], int parent[], bool ap[]) { // A static variable is used for simplicity, we can avoid use of static // variable by passing a pointer. static int time = 0; // Count of children in DFS Tree int children = 0; // Mark the current node as visited visited[u] = true; // Initialize discovery time and low value disc[u] = low[u] = ++time; // Go through all vertices aadjacent to this list<int>::iterator i; for (i = adj[u].begin(); i != adj[u].end(); ++i) { int v = *i; // v is current adjacent of u // If v is not visited yet, then make it a child of u // in DFS tree and recur for it if (!visited[v]) { children++; parent[v] = u; APUtil(v, visited, disc, low, parent, ap); // Check if the subtree rooted with v has a connection to // one of the ancestors of u low[u] = min(low[u], low[v]); // u is an articulation point in following cases // (1) u is root of DFS tree and has two or more chilren. if (parent[u] == NIL && children > 1) ap[u] = true; // (2) If u is not root and low value of one of its child is more // than discovery value of u. if (parent[u] != NIL && low[v] >= disc[u]) ap[u] = true; } // Update low value of u for parent function calls. else if (v != parent[u]) low[u] = min(low[u], disc[v]); } } // The function to do DFS traversal. It uses recursive function APUtil() void Graph::AP() { // Mark all the vertices as not visited bool *visited = new bool[V]; int *disc = new int[V]; int *low = new int[V]; int *parent = new int[V]; bool *ap = new bool[V]; // To store articulation points // Initialize parent and visited, and ap(articulation point) arrays for (int i = 0; i < V; i++) { parent[i] = NIL; visited[i] = false; ap[i] = false; } // Call the recursive helper function to find articulation points // in DFS tree rooted with vertex ‘i‘ for (int i = 0; i < V; i++) if (visited[i] == false) APUtil(i, visited, disc, low, parent, ap); // Now ap[] contains articulation points, print them for (int i = 0; i < V; i++) if (ap[i] == true) cout << i << " "; } // Driver program to test above function int main() { // Create graphs given in above diagrams cout << "\nArticulation points in first graph \n"; Graph g1(5); g1.addEdge(1, 0); g1.addEdge(0, 2); g1.addEdge(2, 1); g1.addEdge(0, 3); g1.addEdge(3, 4); g1.AP(); cout << "\nArticulation points in second graph \n"; Graph g2(4); g2.addEdge(0, 1); g2.addEdge(1, 2); g2.addEdge(2, 3); g2.AP(); cout << "\nArticulation points in third graph \n"; Graph g3(7); g3.addEdge(0, 1); g3.addEdge(1, 2); g3.addEdge(2, 0); g3.addEdge(1, 3); g3.addEdge(1, 4); g3.addEdge(1, 6); g3.addEdge(3, 5); g3.addEdge(4, 5); g3.AP(); return 0; }
Java
// A Java program to find articulation points in an undirected graph import java.io.*; import java.util.*; import java.util.LinkedList; // This class represents an undirected graph using adjacency list // representation class Graph { private int V; // No. of vertices // Array of lists for Adjacency List Representation private LinkedList<Integer> adj[]; int time = 0; static final int NIL = -1; // Constructor Graph(int v) { V = v; adj = new LinkedList[v]; for (int i=0; i<v; ++i) adj[i] = new LinkedList(); } //Function to add an edge into the graph void addEdge(int v, int w) { adj[v].add(w); // Add w to v‘s list. adj[w].add(v); //Add v to w‘s list } // A recursive function that find articulation points using DFS // u --> The vertex to be visited next // visited[] --> keeps tract of visited vertices // disc[] --> Stores discovery times of visited vertices // parent[] --> Stores parent vertices in DFS tree // ap[] --> Store articulation points void APUtil(int u, boolean visited[], int disc[], int low[], int parent[], boolean ap[]) { // Count of children in DFS Tree int children = 0; // Mark the current node as visited visited[u] = true; // Initialize discovery time and low value disc[u] = low[u] = ++time; // Go through all vertices aadjacent to this Iterator<Integer> i = adj[u].iterator(); while (i.hasNext()) { int v = i.next(); // v is current adjacent of u // If v is not visited yet, then make it a child of u // in DFS tree and recur for it if (!visited[v]) { children++; parent[v] = u; APUtil(v, visited, disc, low, parent, ap); // Check if the subtree rooted with v has a connection to // one of the ancestors of u low[u] = Math.min(low[u], low[v]); // u is an articulation point in following cases // (1) u is root of DFS tree and has two or more chilren. if (parent[u] == NIL && children > 1) ap[u] = true; // (2) If u is not root and low value of one of its child // is more than discovery value of u. if (parent[u] != NIL && low[v] >= disc[u]) ap[u] = true; } // Update low value of u for parent function calls. else if (v != parent[u]) low[u] = Math.min(low[u], disc[v]); } } // The function to do DFS traversal. It uses recursive function APUtil() void AP() { // Mark all the vertices as not visited boolean visited[] = new boolean[V]; int disc[] = new int[V]; int low[] = new int[V]; int parent[] = new int[V]; boolean ap[] = new boolean[V]; // To store articulation points // Initialize parent and visited, and ap(articulation point) // arrays for (int i = 0; i < V; i++) { parent[i] = NIL; visited[i] = false; ap[i] = false; } // Call the recursive helper function to find articulation // points in DFS tree rooted with vertex ‘i‘ for (int i = 0; i < V; i++) if (visited[i] == false) APUtil(i, visited, disc, low, parent, ap); // Now ap[] contains articulation points, print them for (int i = 0; i < V; i++) if (ap[i] == true) System.out.print(i+" "); } // Driver method public static void main(String args[]) { // Create graphs given in above diagrams System.out.println("Articulation points in first graph "); Graph g1 = new Graph(5); g1.addEdge(1, 0); g1.addEdge(0, 2); g1.addEdge(2, 1); g1.addEdge(0, 3); g1.addEdge(3, 4); g1.AP(); System.out.println(); System.out.println("Articulation points in Second graph"); Graph g2 = new Graph(4); g2.addEdge(0, 1); g2.addEdge(1, 2); g2.addEdge(2, 3); g2.AP(); System.out.println(); System.out.println("Articulation points in Third graph "); Graph g3 = new Graph(7); g3.addEdge(0, 1); g3.addEdge(1, 2); g3.addEdge(2, 0); g3.addEdge(1, 3); g3.addEdge(1, 4); g3.addEdge(1, 6); g3.addEdge(3, 5); g3.addEdge(4, 5); g3.AP(); } } // This code is contributed by Aakash Hasija
Python
# Python program to find articulation points in an undirected graph from collections import defaultdict #This class represents an undirected graph #using adjacency list representation class Graph: def __init__(self,vertices): self.V= vertices #No. of vertices self.graph = defaultdict(list) # default dictionary to store graph self.Time = 0 # function to add an edge to graph def addEdge(self,u,v): self.graph[u].append(v) self.graph[v].append(u) ‘‘‘A recursive function that find articulation points using DFS traversal u --> The vertex to be visited next visited[] --> keeps tract of visited vertices disc[] --> Stores discovery times of visited vertices parent[] --> Stores parent vertices in DFS tree ap[] --> Store articulation points‘‘‘ def APUtil(self,u, visited, ap, parent, low, disc): #Count of children in current node children =0 # Mark the current node as visited and print it visited[u]= True # Initialize discovery time and low value disc[u] = self.Time low[u] = self.Time self.Time += 1 #Recur for all the vertices adjacent to this vertex for v in self.graph[u]: # If v is not visited yet, then make it a child of u # in DFS tree and recur for it if visited[v] == False : parent[v] = u children += 1 self.APUtil(v, visited, ap, parent, low, disc) # Check if the subtree rooted with v has a connection to # one of the ancestors of u low[u] = min(low[u], low[v]) # u is an articulation point in following cases # (1) u is root of DFS tree and has two or more chilren. if parent[u] == -1 and children > 1: ap[u] = True #(2) If u is not root and low value of one of its child is more # than discovery value of u. if parent[u] != -1 and low[v] >= disc[u]: ap[u] = True # Update low value of u for parent function calls elif v != parent[u]: low[u] = min(low[u], disc[v]) #The function to do DFS traversal. It uses recursive APUtil() def AP(self): # Mark all the vertices as not visited # and Initialize parent and visited, # and ap(articulation point) arrays visited = [False] * (self.V) disc = [float("Inf")] * (self.V) low = [float("Inf")] * (self.V) parent = [-1] * (self.V) ap = [False] * (self.V) #To store articulation points # Call the recursive helper function # to find articulation points # in DFS tree rooted with vertex ‘i‘ for i in range(self.V): if visited[i] == False: self.APUtil(i, visited, ap, parent, low, disc) for index, value in enumerate (ap): if value == True: print index, # Create a graph given in the above diagram g1 = Graph(5) g1.addEdge(1, 0) g1.addEdge(0, 2) g1.addEdge(2, 1) g1.addEdge(0, 3) g1.addEdge(3, 4) print "\nArticulation points in first graph " g1.AP() g2 = Graph(4) g2.addEdge(0, 1) g2.addEdge(1, 2) g2.addEdge(2, 3) print "\nArticulation points in second graph " g2.AP() g3 = Graph (7) g3.addEdge(0, 1) g3.addEdge(1, 2) g3.addEdge(2, 0) g3.addEdge(1, 3) g3.addEdge(1, 4) g3.addEdge(1, 6) g3.addEdge(3, 5) g3.addEdge(4, 5) print "\nArticulation points in third graph " g3.AP() #This code is contributed by Neelam Yadav
時間復雜度:上面的函數是一個含有額外數組的簡單DFS。因此時間復雜度與DFS相同,對於用鄰接表表示的圖,為O(V+E)。
引用
https://www.cs.washington.edu/education/courses/421/04su/slides/artic.pdf
http://www.slideshare.net/TraianRebedea/algorithm-design-and-complexity-course-8
http://faculty.simpson.edu/lydia.sinapova/www/cmsc250/LN250_Weiss/L25-Connectivity.htm
(譯)割點