什么是查找Graph Centroid的高效算法?

时间:2016-09-02 05:02:26

标签: algorithm graph-algorithm depth-first-search centroid connected-components

图形质心是等距离或距离小于或等于(N / 2)的顶点,其中N是通过此顶点连接的连通分量的大小?! [需要更正?!]

这是CodeForces的一个问题,它要求查找每个顶点是否为质心,但是在一次删除和替换一个边之后。

Problem Statement

我需要帮助来改进这种伪码/算法。

Loop all Vertices:
 Loop all edges:
  Position each edge in every empty edge position between two unconnected nodes
  Count Size of each Connected Component (*1).
  If all sizes are less than or equal N/2,
   Then return true

问题是该算法将至少运行O(N * M ^ 2))。这是不可接受的。

我查了解答案,但我无法想出其他人使用的算法的高级抽象。您能帮我理解这些解决方案的工作原理吗?

Solutions' Link

(* 1)DFS Loop

2 个答案:

答案 0 :(得分:0)

我将尝试描述一个在线性时间内解决此问题的不太复杂的算法,以便将来参考我的code(它有一些注释)。

主要思想是你可以将树T置于任意顶点并遍历它,对于每个顶点V,你可以这样做:

  • 从T切割子树V.
  • 找到大小为< = N / 2的最重的顶点H(H可以是T或子树V中的任何一个)。
  • 将子树H移动到子树V的子项。
  • 使用V重新生成T并查找最重的顶点是否具有的大小

以前的算法可以仔细实现以获得线性时间复杂度,问题在于它有很多情况需要处理。

更好的想法是在顶点C找到T的质心C和根T.

将顶点C作为T的根是有用的,因为它保证了C的每个后代都具有

当遍历树时,我们可以避免检查树中最重的顶点但是向上,每次我们访问一个孩子W时,如果我们重新生根T,我们可以传递最重的大小(<= N / 2)在W。

尝试理解我解释的内容,如果有什么不清楚,请告诉我。

答案 1 :(得分:0)

嗯,树的质心可以用O(N)空间和时间复杂度来确定。

  1. 构造表示树的矩阵,其中行索引表示N个节点,第i行中的元素表示第i个节点所连接的节点。您也可以使用任何其他表示形式。

  2. 维护2个大小为N的线性数组,索引i分别代表第i个节点(深度)的深度和第i个节点(父节点)的父节点。

  3. 还要维护另外两个线性数组,第一个包含树(队列)的BFS遍历序列,另一个包含的值[leftOver] [N - 节点中的节点数以该节点为根的子树。换句话说,第i个索引包含当从树中删除第i个节点及其所有子节点时整个树中剩余的节点数。

  4. 现在,以root身份执行任意节点的BFS遍历,并填充数组'parent'和'depth'。这需要O(N)时间复杂度。另外,在数组'队列'中记录遍历序列。

  5. 从叶节点开始,添加以该节点为根的子树中存在的节点数,其值为数组“leftOver”中的父索引。这也需要O(N)时间,因为你可以使用已经准备好的'队列'数组并从后面旅行。

  6. 最后,遍历数组'leftOver'并将每个值修改为[N-1 - 初始值]。准备好'leftOver'数组。费用:另一个O(N)。

  7. 你的工作差不多完成了。现在,迭代这个'leftOver'数组并找到其值最接近floor(N / 2)的索引。但是,该值不得以任何代价超过最低限额(N / 2)。

  8. 此索引是树的质心的索引。总时间复杂度:O(N)。

    Java代码

    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.Scanner;
    
    class Find_Centroid
    {
        static final int MAXN=100_005;
        static ArrayList<Integer>[] graph;
        static int[] depth,parent;  // Step 2
        static int N;
    
        static Scanner io=new Scanner(System.in);
    
        public static void main(String[] args)
        {
            int i;
            N=io.nextInt();
                        // Number of nodes in the Tree
            graph=new ArrayList[N];
    
            for(i=0;i<graph.length;++i)
                graph[i]=new ArrayList<>();
                        //Initialisation
    
            for(i=1;i<N;++i)
            {
                int a=io.nextInt()-1,b=io.nextInt()-1;
                        // Assuming 1-based indexing
                graph[a].add(b);    graph[b].add(a);
                        // Step 1
            }
            int centroid = findCentroid(new java.util.Random().nextInt(N));
                        // Arbitrary indeed... ;)
    
            System.out.println("Centroid: "+(centroid+1));
                        // '+1' for output in 1-based index
        }
    
        static int[] queue=new int[MAXN],leftOver;
                        // Step 3
    
        static int findCentroid(int r)
        {
            leftOver=new int[N];
            int i,target=N/2,ach=-1;
    
            bfs(r);     // Step 4
            for(i=N-1;i>=0;--i)
                if(queue[i]!=r)
                    leftOver[parent[queue[i]]] += leftOver[queue[i]] +1;
                        // Step 5
            for(i=0;i<N;++i)
                leftOver[i] = N-1 -leftOver[i];
                        // Step 6
            for(i=0;i<N;++i)
                if(leftOver[i]<=target && leftOver[i]>ach)
                        // Closest to target(=N/2) but does not exceed it.
                {
                    r=i;    ach=leftOver[i];
                }
                        // Step 7
            return r;
        }
        static void bfs(int root)   // Iterative
        {
            parent=new int[N];  depth=new int[N];
            int st=0,end=0;
            parent[root]=-1;    depth[root]=1;
                    // Parent of root is obviously undefined. Hence -1.
                    // Assuming depth of root = 1
            queue[end++]=root;
            while(st<end)
            {
                int node = queue[st++], h = depth[node]+1;
                Iterator<Integer> itr=graph[node].iterator();
                while(itr.hasNext())
                {
                    int ch=itr.next();
                    if(depth[ch]>0)     // 'ch' is parent of 'node'
                        continue;
                    depth[ch]=h;   parent[ch]=node;
                    queue[end++]=ch;    // Recording the Traversal sequence
                }
            }
        }
    }
    

    现在,对于这个问题,http://codeforces.com/contest/709/problem/E,遍历每个节点i,将其视为根,继续下降具有> N / 2个节点的子节点,并尝试到达一个刚刚拥有的节点在它下面少于N / 2个节点(最接近N / 2个节点)。如果在删除此节点及其所有子节点时使'i'成为质心,则打印'1'否则打印0.此过程可以有效执行,因为'leftOver'数组已经存在。

    实际上,您正在分离令人不安的节点(阻止我成为质心的节点)及其子节点并将其附加到第i个节点本身。子树保证最多有N / 2个节点(如前所述),因此现在不会出现问题。

    快乐编码..... :)