• (译)割点


    注:本文翻译自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[]数组,存储一个顶点被发现的时刻(译者注:即从根节点开始,该点是第几个被访问的)。对于每个节点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 << "
    Articulation points in first graph 
    ";
        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 << "
    Articulation points in second graph 
    ";
        Graph g2(4);
        g2.addEdge(0, 1);
        g2.addEdge(1, 2);
        g2.addEdge(2, 3);
        g2.AP();
     
        cout << "
    Articulation points in third graph 
    ";
        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 "
    Articulation points in first graph "
    g1.AP()
     
    g2 = Graph(4)
    g2.addEdge(0, 1)
    g2.addEdge(1, 2)
    g2.addEdge(2, 3)
    print "
    Articulation 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 "
    Articulation points in third graph "
    g3.AP()
     
    #This code is contributed by Neelam Yadav

    输出:

    Articulation points in first graph
    0 3
    Articulation points in second graph
    1 2
    Articulation points in third graph
    1

    时间复杂度:上面的函数是一个含有额外数组的简单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

  • 相关阅读:
    Android P Beta发布!最新版本抢先体验!
    手游热更新方案--Unity3D下的CsToLua技术
    2018 Unite大会——《使用UPA工具优化项目》演讲实录
    浅谈软件工程师的代码素养
    Android平台的Swift—Kotlin
    1计算机的基本组成-3
    1计算机的基本组成-2
    新的公司
    4 对象的行为 方法操作实例变量
    反射机制
  • 原文地址:https://www.cnblogs.com/collectionne/p/6837787.html
Copyright © 2020-2023  润新知