【问题标题】:What is an Efficient Algorithm to find Graph Centroid?什么是找到图质心的有效算法?
【发布时间】:2016-09-02 05:02:25
【问题描述】:

Graph Centroid 是等距离或距离小于或等于 (N/2) 的顶点,其中 N 是通过该顶点连接的连接组件的大小?! [需要更正吗?!]

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

Problem Statement

我需要帮助来完善这个伪代码/算法。

循环所有顶点: 循环所有边: 将每条边定位在两个未连接节点之间的每个空边位置 每个连接组件的计数大小 (*1)。 如果所有尺寸都小于或等于 N/2, 然后返回真

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

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

Solutions' Link

(*1)DFS Loop

【问题讨论】:

  • 请注意,代码强制问题仅与树有关,与一般图无关。
  • 您能否对这些解决方案文件中使用的算法进行高级伪编码?
  • 大约有 150 份被接受的提交,我检查过的没有一个对文档有太多的困扰......
  • 不是一个完整的算法,只是一个想法:问题陈述要求一个边缘替换。由于图是树,因此每个连接的组件也将是一棵树。通过删除任何边并添加新边,您可以将任意数量的顶点从一个组件移动到另一个组件,因此您“只需”检查是否最多有一个组件具有超过 n/2 个节点,以及是否存在也就是说,存在另一个组件,使得两者组合最多具有 n 个节点。
  • 更正:根据组件的布局,可能并不总是可以找到一条边来切断任意数量的节点,但总体思路应该仍然有效。

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


【解决方案1】:

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

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

  • 从 T 中切出子树 V。
  • 找到大小
  • 移动子树 H 成为子树 V 的子树。
  • 用 V 重新根 T 并查找最重的顶点是否具有大小

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

更好的办法是在顶点 C 处找到 T 的质心 C 和根 T。

将顶点 C 作为 T 的根很有用,因为它可以保证 C 的每个后代的大小

当遍历树时,我们可以避免检查树下最重的顶点,但是向上,每次我们访问一个子 W 时,如果我们将根 T 重新设置为W.

试着理解我解释的内容,如果有不清楚的地方请告诉我。

【讨论】:

  • 谢谢,我会按照这个方法,希望它有效! :)
【解决方案2】:

嗯,一棵树的质心可以在 O(N) 空间和时间复杂度内确定。

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

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

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

  4. 现在,以任意节点为根执行 BFS 遍历,并填充数组“父”和“深度”。这需要 O(N) 时间复杂度。另外,在数组'queue'中记录遍历顺序。

  5. 从叶节点开始,将存在于以该节点为根的子树中的节点数与数组“leftOver”中父索引处的值相加。这也需要 O(N) 时间,因为您可以使用已经准备好的“队列”数组并从后面移动。

  6. 最后,遍历数组'leftOver',将每个值修改为[N-1 - Initial Value]。 'leftOver' 数组已准备好。成本:另一个 O(N)。

  7. 您的工作即将完成。现在,遍历这个 'leftOver' 数组并找到其值最接近 floor(N/2) 的索引。但是,此值不得超过 floor(N/2)。

这个索引是树形心的索引。总体时间复杂度: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 成为质心的节点)及其子节点,并将其附加到第 i 个节点本身。子树保证最多有 N/2 个节点(如前所述),因此现在不会引起问题。

快乐编码..... :)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-01
    • 1970-01-01
    • 2011-05-05
    • 2016-05-29
    • 2010-09-19
    • 2023-04-07
    • 1970-01-01
    相关资源
    最近更新 更多