树的质心是一个节点,如果将其从树中删除,则会将其拆分为“林”,这样林中的任何树最多将具有原始树中顶点数的一半。
假设树中有n个节点。节点的“子树大小”是指以该节点为根的树的大小。
Let S(v) be size of subtree rooted at node v
S(v) = 1 + ? S(u)
Here u is a child to v (adjacent and at a depth one
greater than the depth of v).
Centroid is a node v such that,
maximum(n - S(v), S(u1, S(u2, .. S(um) <= n/2
where ui is i'th child to v.
寻找质心
令T为具有n个节点的无向树。在树中选择任意节点v。如果v满足质心的数学定义,则我们有质心。否则,我们知道我们的数学不等式不成立,并据此得出结论,在v附近存在一些u,使得S(u)> n / 2。我们将u设为新的v并递归。
我们从不重新访问节点,因为当我们决定从其移到子树大小大于n / 2的节点时,我们有点宣称它现在属于节点小于n / 2的组件,因此我们将永远找不到我们的质心在那里。
无论如何,我们都在朝着质心前进。此外,树中的顶点数量有限。该过程必须停止,并且它将停止在所需的顶点。
算法 :
- 选择任意节点v
- 从v启动DFS,并设置子树大小
- 重新定位到节点v(或从属于树的任意v开始)
- 检查质心的数学条件v
- 如果条件通过,则将当前节点作为质心返回
- 否则,移动到具有“最大”子树大小的相邻节点,然后返回到步骤4
定理:给定一棵具有n个节点的树,质心始终存在。
证明:从解决问题的方法中可以清楚地看出,总是可以使用上述步骤找到质心。
时间复杂度
- 选择任意节点v:O(1)
- DFS:O(n)
- 重新定位到v:O(1)
- 查找质心:O(n)
找到树的质心是我们在此要实现的一部分。我们需要考虑如何将树组织成一种结构,以降低回答某些“类型”查询的复杂性。
算法
- 将质心作为新树的根(我们将其称为“质心树”)
- 递归分解生成的森林中的树木
- 将这些树的质心作为最后分裂它们的质心的子代。
质心树的深度为O(lg n),可以在O(n lg n)中构造,因为我们可以在O(n)中找到质心。
说明性的例子
让我们考虑一棵有16个节点的树。该图具有使用节点1的DFS设置的子树大小。
我们从节点1开始,看看质心的条件是否成立。请记住,S(v)是v的子树大小。
我们将节点6设为质心的根,然后递归于森林质心的3棵树上,将原始树分成两部分。
注意:在图中,质心生成的子树被与质心颜色相同颜色的虚线包围。
我们将随后发现的质心作为最后分裂它们的质心的子代,并获得我们的质心树。
注意:仅包含单个元素的树与其质心具有相同的元素。我们尚未对此类树使用颜色区分,而叶节点代表它们。
// C++ program for centroid decomposition of Tree
#include
using namespace std;
#define MAXN 1025
vector tree[MAXN];
vector centroidTree[MAXN];
bool centroidMarked[MAXN];
/* method to add edge between to nodes of the undirected tree */
void addEdge(int u, int v)
{
tree[u].push_back(v);
tree[v].push_back(u);
}
/* method to setup subtree sizes and nodes in current tree */
void DFS(int src, bool visited[], int subtree_size[], int* n)
{
/* mark node visited */
visited[src] = true;
/* increase count of nodes visited */
*n += 1;
/* initialize subtree size for current node*/
subtree_size[src] = 1;
vector::iterator it;
/* recur on non-visited and non-centroid neighbours */
for (it = tree[src].begin(); it!=tree[src].end(); it++)
if (!visited[*it] && !centroidMarked[*it])
{
DFS(*it, visited, subtree_size, n);
subtree_size[src]+=subtree_size[*it];
}
}
int getCentroid(int src, bool visited[], int subtree_size[], int n)
{
/* assume the current node to be centroid */
bool is_centroid = true;
/* mark it as visited */
visited[src] = true;
/* track heaviest child of node, to use in case node is
not centroid */
int heaviest_child = 0;
vector::iterator it;
/* iterate over all adjacent nodes which are children
(not visited) and not marked as centroid to some
subtree */
for (it = tree[src].begin(); it!=tree[src].end(); it++)
if (!visited[*it] && !centroidMarked[*it])
{
/* If any adjacent node has more than n/2 nodes,
* current node cannot be centroid */
if (subtree_size[*it]>n/2)
is_centroid=false;
/* update heaviest child */
if (heaviest_child==0 ||
subtree_size[*it]>subtree_size[heaviest_child])
heaviest_child = *it;
}
/* if current node is a centroid */
if (is_centroid && n-subtree_size[src]<=n/2)
return src;
/* else recur on heaviest child */
return getCentroid(heaviest_child, visited, subtree_size, n);
}
/* function to get the centroid of tree rooted at src.
* tree may be the original one or may belong to the forest */
int getCentroid(int src)
{
bool visited[MAXN];
int subtree_size[MAXN];
/* initialize auxiliary arrays */
memset(visited, false, sizeof visited);
memset(subtree_size, 0, sizeof subtree_size);
/* variable to hold number of nodes in the current tree */
int n = 0;
/* DFS to set up subtree sizes and nodes in current tree */
DFS(src, visited, subtree_size, &n);
for (int i=1; i::iterator it;
/* for every node adjacent to the found centroid
* and not already marked as centroid */
for (it=tree[cend_tree].begin(); it!=tree[cend_tree].end(); it++)
{
if (!centroidMarked[*it])
{
/* decompose subtree rooted at adjacent node */
int cend_subtree = decomposeTree(*it);
/* add edge between tree centroid and centroid of subtree */
centroidTree[cend_tree].push_back(cend_subtree);
centroidTree[cend_subtree].push_back(cend_tree);
}
}
/* return centroid of tree */
return cend_tree;
}
// driver function
int main()
{
/* number of nodes in the tree */
int n = 16;
/* arguments in order: node u, node v
* sequencing starts from 1 */
addEdge(1, 4);
addEdge(2, 4);
addEdge(3, 4);
addEdge(4, 5);
addEdge(5, 6);
addEdge(6, 7);
addEdge(7, 8);
addEdge(7, 9);
addEdge(6, 10);
addEdge(10, 11);
addEdge(11, 12);
addEdge(11, 13);
addEdge(12, 14);
addEdge(13, 15);
addEdge(13, 16);
/* generates centroid tree */
decomposeTree(1);
return 0;
}
输出 :
6 4 1 2 3 5 7 8 9 11 10 12 14 13 15 16
应用:
考虑下面的示例问题
Given a weighted tree with N nodes, find the minimum number
of edges in a path of length K, or return -1 if such a path
does not exist.
1 <= N <= 200000
1 <= length(i;j) <= 1000000 (integer weights)
1 <= K <= 1000000
蛮力解决方案:对于每个节点,执行DFS查找到每个其他节点的距离和边数
时间复杂度:O(N 2 )显然无效,因为N = 200000
我们可以使用质心分解在O(N Log N)时间内解决上述问题。
- 执行质心分解以获得“子树树”
- 从分解的根部开始,如下解决每个子树的问题
- 解决当前子树的每个“子树”的问题。
- 从当前子树上的质心执行DFS,以计算包含质心的路径的最小边数
- 两种情况:质心在路径的末端或中间
使用带时间戳的大小为1000000的数组来跟踪距质心的可能距离以及该距离的最小边数
取上述两个中的最小值
- 两种情况:质心在路径的末端或中间
基于质心分解的解决方案的时间复杂度为O(n log n)
参考 :
http://www.ugrad.cs.ubc.ca/~cs490/2014W2/pdf/jason.pdf