什么是找到图形质心的有效算法?
What is an Efficient Algorithm to find Graph Centroid?
图质心是距离相等或距离小于或等于 (N/2) 的顶点,其中 N 是通过该顶点连接的连通分量的大小?! [需要更正?!]
这是 CodeForces 上的一个问题,它要求查找每个顶点是否是质心,但是在一次只删除并替换一条边之后。
我需要帮助来完善这个伪代码/算法。
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))。不能接受。
我查了答案,但我想不出其他人使用的算法的高级抽象。你能帮我理解这些解决方案是如何工作的吗?
(*1) DFS Loop
我将尝试向您描述一种在线性时间内解决此问题的不太复杂的算法,以供将来参考,请参阅我的code(它有一些评论)。
主要思想是你可以在任意顶点处建立树 T 并遍历它,对于每个顶点 V 你可以这样做:
- 从T中切出子树V
- 找到大小<= N/2的最重顶点H(H可以在任何T或子树V中)。
- 移动子树 H 成为子树 V 的子树。
- 用 V 对 T 求根并查找最重的顶点是否具有大小 <= N/2
前面的算法可以仔细实现线性时间复杂度,问题是它有很多情况需要处理。
更好的想法是找到T的质心C和顶点C处的根T。
将顶点 C 作为 T 的根很有用,因为它保证了 C 的每个后代的大小 <= N/2。
当遍历树时,我们可以避免在树下检查最重的顶点,但是在树上,每次我们访问子 W 时,我们可以传递最重的大小(<= N/2)如果我们在 W 重新根 T。
试着理解我解释的内容,如果有什么不清楚的地方,请告诉我。
嗯,一棵树的质心可以在O(N)space和时间复杂度中确定。
构造一个表示树的矩阵,行索引表示N个节点,第i行的元素表示第i个节点所连接的节点。您也可以使用任何其他表示形式。
维护2个大小为N的线性数组,索引i分别代表第i个节点的深度(depth)和第i个节点的父节点(parent)
另外维护2个线性数组,第一个包含树(队列)的BFS遍历序列,另一个(leftOver)包含[N的值- 以该节点为根的子树中的节点数]。换句话说,第 i 个索引包含当第 i 个节点连同它的所有子节点一起从树中删除时整棵树中剩余的节点数。
现在,以任意节点为根进行BFS遍历,填充数组'parent'和'depth'。这需要 O(N) 时间复杂度。另外,在数组'queue'.
中记录遍历顺序
从叶节点开始,添加以该节点为根的子树中存在的节点数,值为数组 'leftOver' 中父索引处的值。这也需要 O(N) 时间,因为您可以使用已经准备好的 'queue' 数组并从后向移动。
最后遍历数组'leftOver',修改每个值为[N-1 - Initial Value]。 'leftOver' 数组已准备就绪。成本:另一个 O(N).
你的工作快完成了。现在,迭代此 '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 个节点(如前所述),因此现在不会造成问题。
快乐编码......:)
图质心是距离相等或距离小于或等于 (N/2) 的顶点,其中 N 是通过该顶点连接的连通分量的大小?! [需要更正?!]
这是 CodeForces 上的一个问题,它要求查找每个顶点是否是质心,但是在一次只删除并替换一条边之后。
我需要帮助来完善这个伪代码/算法。
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))。不能接受。
我查了答案,但我想不出其他人使用的算法的高级抽象。你能帮我理解这些解决方案是如何工作的吗?
(*1) DFS Loop
我将尝试向您描述一种在线性时间内解决此问题的不太复杂的算法,以供将来参考,请参阅我的code(它有一些评论)。
主要思想是你可以在任意顶点处建立树 T 并遍历它,对于每个顶点 V 你可以这样做:
- 从T中切出子树V
- 找到大小<= N/2的最重顶点H(H可以在任何T或子树V中)。
- 移动子树 H 成为子树 V 的子树。
- 用 V 对 T 求根并查找最重的顶点是否具有大小 <= N/2
前面的算法可以仔细实现线性时间复杂度,问题是它有很多情况需要处理。
更好的想法是找到T的质心C和顶点C处的根T。
将顶点 C 作为 T 的根很有用,因为它保证了 C 的每个后代的大小 <= N/2。
当遍历树时,我们可以避免在树下检查最重的顶点,但是在树上,每次我们访问子 W 时,我们可以传递最重的大小(<= N/2)如果我们在 W 重新根 T。
试着理解我解释的内容,如果有什么不清楚的地方,请告诉我。
嗯,一棵树的质心可以在O(N)space和时间复杂度中确定。
构造一个表示树的矩阵,行索引表示N个节点,第i行的元素表示第i个节点所连接的节点。您也可以使用任何其他表示形式。
维护2个大小为N的线性数组,索引i分别代表第i个节点的深度(depth)和第i个节点的父节点(parent)
另外维护2个线性数组,第一个包含树(队列)的BFS遍历序列,另一个(leftOver)包含[N的值- 以该节点为根的子树中的节点数]。换句话说,第 i 个索引包含当第 i 个节点连同它的所有子节点一起从树中删除时整棵树中剩余的节点数。
现在,以任意节点为根进行BFS遍历,填充数组'parent'和'depth'。这需要 O(N) 时间复杂度。另外,在数组'queue'.
中记录遍历顺序
从叶节点开始,添加以该节点为根的子树中存在的节点数,值为数组 'leftOver' 中父索引处的值。这也需要 O(N) 时间,因为您可以使用已经准备好的 'queue' 数组并从后向移动。
最后遍历数组'leftOver',修改每个值为[N-1 - Initial Value]。 'leftOver' 数组已准备就绪。成本:另一个 O(N).
你的工作快完成了。现在,迭代此 '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 个节点(如前所述),因此现在不会造成问题。
快乐编码......:)