在图中找到访问量最大的节点

时间:2018-11-23 16:44:27

标签: graph tree least-common-ancestor

在图中,有N个节点正好由N-1条边连接。从一个节点到任何其他节点之间只有一条最短的路径。节点的编号从1到N。给定的Q查询告诉源节点和目标节点。在经过这些Q路径后,找到访问量最大的节点。例如说Q = 3 和3个查询是:

1 5

2 4

3 1

因此从节点1到节点5,然后从节点2到节点4,再从节点3到节点1。最后,在Q查询之后找到访问量最大的节点。 寻找每个路径并增加每个访问的节点数是一种幼稚的方法。面试官要我优化它。

1 个答案:

答案 0 :(得分:1)

优化通常需要权衡;在某些情况下,一种算法明显优于另一种算法,但是在其他情况下,一种算法在一个方面(例如时间)更好,而另一种算法在不同方面(例如内存使用量)更好。

在您的情况下,我猜测您的面试官正在寻找一种优化您开始接收查询后必须完成的处理量的方法,即使这表示您已经在图形上进行更多预处理。我这样说的原因是“查询”一词;为“在线”查询优化数据源是很普遍的。 (当然,他可能没想到您会自己决定这种权衡是可以的;相反,他可能希望就各种权衡进行对话。)

因此,请牢记这一点。 。

  • 我看到您已经用[tree]和[least-common-answer]标记了您的问题,所以您大概已经做了最大的观察,即:
    • 图形是一棵树。我们可以任意选择一个“根”,以便每个其他节点都有一个“父”,一个非零“深度”,一个或多个“祖先”等。
    • 完成此操作后,从节点 a 到节点 b 的最短路径包括节点 a ,节点 b ,不是 b 祖先的所有 a 祖先,不是 b祖先的所有 b 祖先> a ,以及他们的“最小共同祖先”。 (如果 a b 的祖先,反之亦然:如果 a b 的祖先, ,则它是 a b 的最不常见祖先,反之亦然,如果 a b 一样。)
  • 因此,我们可以进行以下预处理:
    • 将图表示为从每个节点到其邻居列表的映射。 (由于节点的编号从1到 N ,因此此映射是 N 个列表的数组。)
    • 选择一个根节点。
    • 计算并存储每个节点的“父级”和“深度”。 (我们可以使用深度优先搜索或宽度优先搜索在 O N )时间内完成此操作。)
    • 对于每对节点,计算并存储其“最小共同祖先”。 (我们可以使用上一步和备注的结果在总时间 O N 2 中进行此操作),因为备注提供了摊销。 )
    • 初始化从每个节点到它作为路径端点的次数的映射,以及从每个节点到它是路径端点的最不祖先的次数的映射。 (请注意:如果给定路径是从单个节点到其自身的路径,则我们将其视为路径的终点两次-并将其视为终点的最后一个共同祖先。)
  • 对于每个查询,更新两个映射。我们可以在每个查询的 O (1)时间中完成此操作,总共需要 O Q )时间。
  • 最后:
    • 对图形进行后遍历,计算访问该节点的路径数。这样做的逻辑如下:访问节点 a 的路径总数等于访问其每个子节点的路径总数之和,减去访问该子节点的路径总数它的每个子对象都是路径端点的最后一个共同祖先,加上 a 本身是端点的次数,再减去 a 本身是端点的次数。路径端点的最后一个共同祖先(以消除重复计数)。
    • 返回上一步为其返回最大数目的节点。如果多个节点并列最大,则。 。 。我不知道,问题陈述对此含糊不清,您需要提出要求。

总体而言,这需要进行 O N 2 )预处理, O Q )每个查询的实时处理,以及 O N )后处理。

如果 N 很大,并且我们希望甚至只访问一小部分节点,那么我们可以通过忽略树的未访问部分来加快后处理的速度。这涉及维护一组作为路径端点的节点,然后以“自下而上”的方式进行后处理,从此类节点的最深处开始,并且仅当访问的路径数达到一定数量时,才从给定节点向“父方向”移动该节点的数量少于它作为共同祖先的次数。如果我们用 P 表示不同端点的数量,用 M 表示不同访问的节点的数量,则可以用 O 之类的方法完成。 ( P log P ++ M )。