【问题标题】:Algorithm/Data Structure for finding combinations of minimum values easily轻松找到最小值组合的算法/数据结构
【发布时间】:2011-06-03 12:52:24
【问题描述】:

我有一个对称矩阵,如下图所示。

我已经编写了符号 A.B 表示网格点 (A, B) 处的值。 此外,编写 A.B.C 给出我是这样的最小网格点值:MIN((A,B), (A,C), (B,C))。

作为另一个例子 A.B.D 给了我 MIN((A,B), (A,D), (B,D))。

我的目标是一次找到一行的所有字母组合(不重复)的最小值,例如对于这个例子,我需要找到计算给出的关于 A 行的最小值:

A.B = 6
交流 = 8
公元 = 4
A.B.C = MIN(6,8,6) = 6
A.B.D = MIN(6, 4, 4) = 4
A.C.D = MIN(8, 4, 2) = 2
A.B.C.D = MIN(6, 8, 4, 6, 4, 2) = 2

我意识到某些计算可以重用,随着矩阵大小的增加,这变得越来越重要,但问题是找到实现这种重用的最有效方法。

能否指出正确的方向来找到可用于解决此问题的有效算法/数据结构?

【问题讨论】:

  • B.C、B.D 和 C.D 怎么样?为什么你的目标不包括这些?
  • 我需要逐行处理的值。所以我只需要先找到 A 行的最小值,对这些值进行一些计算,然后转到 B 行。
  • 那么你的目标,如上所述,不完整?您已经说明了 A 行的目标,但您也有其他行的目标?
  • 是的,抱歉,我刚刚更新了问题。
  • 如果行是独立的,为什么还要把它看成一个矩阵呢?一次只考虑一行......还是我错过了什么?

标签: algorithm data-structures


【解决方案1】:

您需要考虑lattice of subsets of the letters, ordered by inclusion。本质上,您为每个大小为 2 的子集 S (即矩阵的每个非对角线元素 - 对角线元素似乎不会出现在您的问题中)给出了一个值 f(S),问题是对于大小大于 2 的每个子集 T,找到 T 中包含的所有大小为 2 的 S 的最小值 f(S)。(然后您只对包含某个元素“A”的集合 T 感兴趣——但我们暂时不考虑。)

首先,请注意,如果您有 n 个字母,这相当于问Omega(2^n) 个问题,每个子集大约一个。 (排除零元素和一元素子集以及不包含“A”的子集可以分别为您节省 n + 1 个集合和两倍的因子,这对于 big Omega 是允许的。)因此,如果您想存储所有即使是中等大小的 n,这些答案也需要大量内存。如果 n 在您的应用程序中很大,最好存储一些预先计算的数据集合,并在您需要特定数据点时进行一些计算;我还没有考虑过什么会最有效,但是例如,只为晶格中包含的二叉树计算数据并不一定能帮助你,除了什么都不预计算。

解决了这些问题,让我们假设您实际上想要计算所有答案并将其存储在内存中。您需要“逐层”计算这些,即从三元素子集开始(因为矩阵已经给出了二元素子集),然后是四元素,然后是五元素等。这样,对于给定的子集 S,当我们计算 f(S) 时,我们已经计算了严格包含在 S 中的 T 的所有 f(T)。有几种方法可以利用它,但我认为最简单的可能是使用两个这样的子集 S:让 t1 和 t2 成为 T 的两个不同元素,您可以随意选择它们;令 S 为移除 t1 和 t2 时得到的 T 的子集。为 S 加 t1 写 S1,为 S 加 t2 写 S2。现在 T 中包含的每一对字母要么完全包含在 S1 中,要么完全包含在 S2 中,要么就是 {t1, t2}。在之前计算的值中查找 f(S1) 和 f(S2),然后直接在矩阵中查找 f({t1, t2}),并存储 f(T) = 这三个数字中的最小值。

如果您从不为 t1 或 t2 选择“A”,那么实际上您可以计算您感兴趣的所有内容,而无需为任何不包含“A”的集合 T 计算 f。 (这是可能的,因为上述步骤仅在 T 包含至少三个元素时才有意义。)好!这只剩下一个问题——如何存储计算值 f(T)。我要做的是使用 2^(n-1) 大小的数组;用 (n-1) 位数表示每个包含“A”的字母子集,其中只要第 (i+1) 个字母在该集合中,第 i 位就为 1(所以 0010110,其中设置了第 2、4 和 5 位,表示字母表“A”..“H”中的子集 {“A”、“C”、“D”、“F”} - 注意我从开始计算位从右边 0 开始,从“A”开始的字母 = 0)。这样,您实际上可以按数字顺序遍历集合,而无需考虑如何遍历 n 元素集合的所有 k 元素子集。 (当考虑的集合有 0 或 1 个元素时,您确实需要包含一个特殊情况,在这种情况下您什么都不想做,或者 2 个元素,在这种情况下您只需从矩阵中复制值。)

【讨论】:

  • 我可以澄清一下迭代子集吗?你说我可以按数字顺序遍历子集,但如果我将 1 添加到 0010110,我会得到 0010111,它现在是 5 个字母子集的一部分。这如何帮助我逐层遍历所有子集?
  • 对不起 - 我不是很清楚。从抽象的角度考虑算法时,我发现最容易以逐层的方式来考虑它,首先做三个元素的子集,然后是四个,等等。一旦你开始实现,它实际上更容易以数字顺序实现,它不是逐层的,但仍然具有基本属性:在处理任何子集 S 之前,先处理 S 的所有子集。
【解决方案2】:

嗯,这对我来说看起来很简单,但也许我误解了这个问题。我会这样做:

  • P 成为符号X1.X2. ... .Xn 中的模式字符串,其中Xi 是矩阵中的一列

  • 首先计算数组CS = [ (X1, X2), (X1, X3), ... (X1, Xn) ],其中包含X1 与模式中所有其他元素的所有组合; CSn-1 元素,你可以在 O(n) 中轻松构建它

  • 现在你必须计算min (CS),即找到与CS中的组合对应的矩阵元素的最小值;再次,您可以轻松找到 O(n) 中的最小值

  • 完成。

注意:由于您的矩阵是对称的,给定P,您只需通过将P 的第一个元素与所有其他元素组合来计算CS(X1, Xi) 等于(Xi, X1)


如果你的矩阵很大,想要做一些优化,可以考虑P的前缀:让我举例说明

  • 当您解决了P = X1.X2.X3 的问题后,将结果存储在关联映射中,其中X1.X2.X3 是键

  • 稍后,当您解决问题 P' = X1.X2.X3.X7.X9.X10.X11 时,您会在地图中搜索最长的前缀 P':您可以从 P' 开始并删除一个组件 (Xi)从最后一次直到您在地图中找到匹配项或您最终得到一个空字符串

  • 如果您在映射中找到前缀 P',那么您已经知道该问题的解决方案,因此您只需找到将前缀的第一个元素与后缀,然后比较两个结果:在我们的示例中,前缀是X1.X2.X3,因此您只需解决问题 X1.X7.X9.X10.X11,然后比较两个值并选择最小值(别忘了用新模式更新你的地图P'

  • 如果你没有找到任何前缀,那么你必须为P'解决整个问题(同样不要忘记用结果更新地图,这样你以后可以重复使用它)

这种技术本质上是memoization的一种形式。

【讨论】:

  • 好吧,这行得通,但我想知道如何有效地重用计算。因此,例如在 X1.X2.X3 中完成的计算,可以在 X1.X2.X3.X4、X1.X2.X3.X4.X5 等中重复使用。我应该如何存储以前的计算,以便于重新访问和使用?
  • @Projectile Fish:我编辑了我的答案以解决您的评论。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-15
  • 1970-01-01
  • 2021-11-21
  • 1970-01-01
  • 1970-01-01
  • 2019-01-19
  • 1970-01-01
相关资源
最近更新 更多