优化了带有矩阵的极密集图的 dijkstra 算法

问题描述

您有 N 个节点,标记为 1…N (N

您还得到一个矩阵 M x M,以确定不同 ID 的节点之间是否存在有向边。 M_ij = ‘Y’ 如果 ID i 的节点有指向 ID j 的节点的有向边,如果没有,则 = ‘N’。请注意,M_ij 可能不等于 M_ji,并且 M_ii 可能 = 0。

如果两个节点之间有一条边,那么它们之间有向边的权重就是它们标签间的差值。

示例:

有 5 个节点。

节点 1:ID 1

节点 2:ID 4

节点 3:ID 2

节点 4:ID 3

节点 5:ID 4

矩阵:

YNYN

NNNY

纽约

妮妮

所以在这个矩阵中,我们知道 ID 1 的节点有指向 ID 1 和 ID 3 的节点的边。ID 2 的节点有指向 ID 4 的节点的边。ID 3 的节点有指向ID 2 和 ID 3。ID 4 的节点有指向 ID 2 节点的边。

答案 = 6。

节点 1 和 5 之间的最短路径是通过从节点 1 到节点 4 的有向边,权重为 4 - 1 = 3。请注意,这条边是可能的,因为节点 1 的 ID 为 1,节点 4 的 ID 为ID 3 和 ID 1 的节点具有到 ID 3 的所有节点的有向边。接下来,您将使用从节点 4 到节点 3 的有向边,权重为 4 - 3 = 1;那么从节点3到节点5的有向边,权重为5 - 3 = 2。3 + 1 + 2 = 6,所以总的最短路径为6。

我的解决方案:

当我第一次阅读这个问题时,我认为 Dijkstra 的算法会很容易解决它。然而,它的时间复杂度是 N^2logN,边的数量最多可能是 50000^2,这太高了。如何解决密集图中的最短路径问题?有没有办法通过某种方式利用矩阵来优化 Dijkstra?

解决方法

假设有 3 个具有不同标签的节点共享相同的 ID 1
ID 1 <==> Labels {1,7,13}

同理,有5个节点共享相同的id2
ID 2 <==> Labels {2,8,12,15}

3 个共享相同 ID 的节点3
ID 3 <==> Labels {5,10,11}

和2个共享相同ID的节点4
ID 4 <==> Labels {6,9}

现在假设我们有 4 个边:

  • ID1 -> ID2
  • ID1 -> ID3
  • ID2 -> ID4
  • ID3 -> ID4

如果我们想找出标签“7”和“9”之间的最小距离,我们该怎么做?

井标签 7 表示 ID1,标签 9 表示 ID4。这意味着我们需要找到 ID1 和 ID4 之间的最短距离。

但是由于这 2 个 ID 之间没有直接边缘,我们必须先跳到 ID 2 或 3。

让我们计算到 ID 2 的距离:
我们应该从 ID1 跳转到 ID2 内的哪个标签?
由于距离定义为 2 个标签值之间的绝对差,而源标签是 '7',所以我们应该跳到最接近 7 值的标签。
在{1,2,15}中,这将是'8'(距离= 8-7 = 1)

让我们计算到 ID 3 的距离:
我们应该从 ID1 跳转到 ID3 中的哪个标签?
在 {5,11} 中,最接近 7 的数字是 '5'(距离 = 7-5 = 2)

由于到 ID2 的距离小于 ID3,我们应该跳到 ID2,标签 8。

现在我们在标签8处,我们可以直接跳转到ID4中的标签9(距离= 9-8 = 1)

总距离 = 1 + min(1,2) + 1 = 3

一般算法是:

  • 在 ID 和共享该 ID 的标签的排序列表之间创建一个哈希图。
  • 如果源标签是 L1,目标标签是 L2,假设它们属于 I1 和 I2,执行 dijkstra 以找到 I1 和 I2 之间的最短距离。
  • 在执行 dijkstra 时,通过在共享该 id 的标签列表中选择值最接近 L1 的标签来计算当前标签 'L1' 和邻居 id 之间的“距离”(该列表可以从哈希图访问).
  • 对于从 L1 到邻居 ID 的所有此类距离,将所有这些距离(连同所选标签)推送到优先级队列(与普通 dijkstra 相同)。

在排序数组 can be done in O(logn) 中查找最接近某个值的数字,其中 n 是 排序数组的大小。

dijkstra 的算法复杂度为 O(M^2 * logM * logX),其中 X = 共享单个 ID 的平均标签数,可以安全地假设为 N / M。

使用排序列表创建哈希图需要 O(M*X*logX) = O(N * logN/M) 复杂度。

所以总复杂度 = O((N * log(N/M)) + (M^2 * logM * log(N/M)) ~O(log(N/M) * (N + M^2)

,

免责声明:
据我了解,边缘 ID1 -> ID2 意味着从 ID1 的节点到每个其他 ID2 的节点都有一条边。如果这不正确,只需忽略其余部分。现在我假设这是事实。

观察
由于边 ID1->ID2 意味着具有 ID1 的每个节点与具有 ID2 的每个其他节点都有一条边,所以我们使用具有特定 ID 的节点并不重要 - 它们对于最短路径目的都是等效的。
这意味着要计算从节点 Label_X,ID_I 到节点 Label_Y,ID_J 的最短路径的长度,我们可以简单地计算一个图上的 Djikstra,该图考虑每个标签都只出现一次,以找到 标签上的最短路径。这将需要 O(M * log(M^2)) 次操作。

现在,由于我们有了标签路径,我们可以简单地选择我们遇到的带有所需标签的第一个节点。由于无论如何我们都必须读取输入,我们可以简单地创建一个数组,其中包含我们遇到的每个 ID 的第一个节点。读取输入需要~ N + M^2操作
在这种情况下,总复杂度为 O(N + M^2 + M * log(M^2))。在您的情况下,总数将为 50000 + 2500 + 50 * log(2500) = ~ 53100

,

在具有 M 个节点的较小图上运行 Dijkstra 算法,而不是在具有 N 个节点的较大图上运行 Dijkstra 算法,即将每个指定的 ID 视为一个节点,并在该图上解决问题。

例如,如果节点1的指定ID为A,节点N的指定ID为B,则从A求解最短路径到 B 使用由 MM 矩阵给出的边缘信息。构建路径很容易 - 只需从路径中每个“元”指定 ID 节点中选择任何“真实”节点即可。

Dijkstra 算法的时间复杂度取决于用于边界集的数据结构。由于您声称它是一个“密集”图(通常意味着边数与节点数的平方成正比),因此如果您使用二元平均堆,则使用此方法将为您提供 O(M^2logM) 。如果您使用未排序的数组,它将是 O(M^2),这对于密集图会更好。

请注意,此方法仅适用于当 M_ij = Y 时,指定 ID 为 i 的所有节点都具有到指定 ID 为 j 的所有节点的传出边。