一棵树的中心点?

时间:2012-02-09 04:50:25

标签: algorithm graph tree

假设我们有一个有根的有序树。对于每个节点,我们都有一个子链接列表。 P [i]是节点i与所有其他节点的距离之和。有没有一种算法我们可以找到一个树的P [i]最小的节点(可能是几个相等的P [i]),在最坏的情况下花费O(n)时间?

3 个答案:

答案 0 :(得分:2)

这是一些有效的O(N)代码。在这个例子中,我使用图{0:[1,2,3],1:[4,5],2:[6]}

我把它编码好玩。对于下图,它发现中心是节点0,其P [i]值为9.来自i-> P [i]的映射为{0:9,1:10,2:12,3:14 ,4:15,5:15,6:17}

nodes={0:[1,2,3],1:[4,5],2:[6]}
PB={}
NB={}
def below(i):
    if i not in nodes:
        PB[i]=0
        NB[i]=1
        return 0,1
    tot_nodes_below=0
    tot_paths_below=0
    for node in nodes[i]:
        paths_below,nodes_below=below(node)
        tot_nodes_below+=nodes_below
        tot_paths_below+=paths_below
    tot_paths_below+=tot_nodes_below
    tot_nodes_below+=1
    PB[i]=tot_paths_below
    NB[i]=tot_nodes_below
    return tot_paths_below,tot_nodes_below

P={0:below(0)[0]}
def fill_P(i):
    for node in nodes[i]:
        P[node]=P[i]+7-2*NB[node] #7 is the number of nodes
        if node in nodes:
            fill_P(node)
fill_P(0)

_min=min(P.values())
answers=[k for k in P if P[k]==_min]
print answers
"[0]"

说明: 这段代码是O(N)(我认为对吗?)

基本上node = dict,显示连接到其子节点的每个父节点。

让T [i]成为“树i”。我将其定义为从节点i开始的子树。 (例如T [2] = 2:6,而T [0]是整个树,T [1]将是1:[4,5]。)

现在NB [i]是T [i]中的节点数。

NB = {0:7,1:3,2:2,3:1,4:1,5:1,6:1}

PB [i]是T [i]内节点距离的总和。 (所以PB [i]基本上是P [i],除了我们只看T [i]而不是T [0]。

PB = {0:9,1:2,2:1,3:0,4:0,5:0,6:0}

参见PB [0] = 9,因为在T [0]中有9条路径变为0。 PB [6] = 0,因为NB [0] = 1等。

因此,为了实际构建PB和NB,我们需要递归O(N)函数“下面(i)”。

下面(i)从根节点开始,沿着每个子树T [i]向下运行。对于每个子树,它计算出NB [i]和PB [i]。注意,如果节点没有子节点,PB [i] = 0且NB [i] = 1,则递归的基本情况是微不足道的。

为了计算具有子节点的节点的PB [i]和NB [i],我们使用递归公式。 设节点i有子节点x1..xj,然后NB [i] = 1 + sum(NB [x])。

有一个类似的递归公式来计算PB [i]。

PB [i] = SUM(PB [x])+ NB [i]我们添加NB [i]的原因是因为下面的每个节点必须行进额外的距离1以从子树T [x]到达节点i。

一旦我们的功能(i)填充了NB和PB,我们就可以使用这两个结果来找出P.

fill_P(i)使用NB和PB来做这件事。

如果节点i和j彼此靠近,则认为P [i]将接近P [j]的值。

实际上让我们看看我们是否可以使用NB [1],PB [1]和P [0]计算出P [1]。

结果是P [1] = P [0] + 7-2 * NB [1] (我们甚至不需要使用PB的结果,但是我们需要PB来获得初始的P [0]值)。 为了解这个公式为什么是真的,想想为什么P [1]不等于P [0]。它有助于获得树的图片。让我们通过删除节点1将树分成两部分。现在这给出了树的左侧(不包括节点0)和树的右侧(包括节点0)。注意树的左侧只是T [1],我们有结果NB [1]。 P [1]与P [0]相同,除了T [1]行程距离1以内的节点的所有路径之外。来自不在T [1]中的节点的所有路径进一步传播(通过节点0到达节点1)。路径数分别仅为NB [1]和7-NB [1]。所以我们得到P [1] = P [0] +(7-NB [1]) - NB [1],它给出了我们需要的公式。

现在我们有P [0]和P [1]的正确P值。我们可以计算节点1或节点0的任何子节点的值.fush_P只是通过应用该公式的每个子节点,我们留下结果P.我们只是迭代P来找到最小值,这就是我们的结果。

希望现在欢呼这是有道理的。

答案 1 :(得分:1)

我认为您可以在时间O(n)中为所有节点计算P [i] - 当然包括内部节点,这将倾向于具有小P [i]的节点。在此之后,找到最小的P [i]是一个额外的O(n),所以总和是O(n)。

考虑在节点之间发送消息。从任意节点开始,可以是根节点。对于该节点的每个邻居,发送有关节点和总长度的信息请求。接收来自每个邻居的消息,给出以该邻居为根的子树中的节点数,以及该子树中所有节点与该邻居的总距离。

从这个工作中找出根的P [i],并向每个邻居发送一条消息,给出除了根据该邻居的子树之外的所有树中的节点数和总距离。

在每个节点而不是发起方中,将第一条消息作为类似请求传播给除请求中发送的所有邻居之外的所有邻居。总结节点数。对于每个距离,添加与其关联的节点计数的乘积以及与答复中发送的邻居的距离。总计这些金额以将回复发送回原始请求。

当您从发起人那里收到一条消息,给出除了您的子树之外的整个树的距离和计数总和,将其与您发回的消息中的信息结合起来计算出您的P [i]和相似的总数您发送回发送查询消息的节点的消息。

这最终在所有节点处计算P [i]。节点之间的每个链路只能看到少量的消息。每条消息只需要很少量的工作(一些小计需要计算为一组总数 - 少量)。所以费用是O(n)。

答案 2 :(得分:0)

您只需要为每个节点找到距离它最远的节点的距离,而不是总和。那些距离最短的人将成为树的“中心”。

对于算法,您可以查看herehere

[edit]使用总和时可能不完整(或更糟)的答案:
取{R:(a),a:(b,c),c:(d)}然后得到如下的总和:
R - 8
a - 5
b - 8
c - 6
d - 9
它明确地将a作为中心点

然而,当你得到'传统方法时: R-d 3
a-d 2
b-d 3
c-R | b 2
d-R | b 3
其中ac为中心点

这显示了至少一种情况,即使用总和将导致不完整的答案。这会提示一个问题,即是否存在sums-method会给出错误答案的情况。