【问题标题】:Finding n-th biggest product in a large matrix of numbers, fast快速找到大型数字矩阵中的第 n 个最大乘积
【发布时间】:2012-05-17 13:50:57
【问题描述】:

我正在研究一种适用于大量项目的排序/排名算法,我需要以一种有效的方式实现以下算法以使其工作:


有两个数字列表。它们同样长,大约 100-50 万条。从这里我需要找到这些列表之间的第 n 个最大的产品,即。如果您创建一个矩阵,其中顶部有一个列表,一侧有另一个列表,每个单元格都是上面数字和侧面数字的乘积。

示例:列表为A=[1, 3, 4]B=[2, 2, 5]。那么产品是[2, 2, 5, 6, 6, 15, 8, 8, 20]。如果我想要第三大,那就是 8。

简单的解决方案是简单地生成这些数字,对它们进行排序,然后选择第 n 个最大的。但那是O(m^2 * log m^2),其中 m 是小列表中元素的数量,这还不够快。

我认为我需要先对两个小列表进行排序。那是O(m * log m)。然后我肯定知道最大的一个A[0]*B[0]。第二大的是 A[0]*B[1] 或 A[1]*B[0], ...

我觉得这可以在O(f(n)) 步骤中完成,与矩阵的大小无关。但是我想不出一种有效的方法来完成这部分。


编辑:有一个答案被删除,建议记住两个排序集中的位置,然后查看 A[a]*B[b+1] 和 A[a+1]*B[b] ,返回较大的一个并递增 a/b。我打算在它被删除之前发布这条评论:

这行不通。想象两个列表 A=B=[3,2,1]。这会给你 像 [9,6,3 ; 6,4,2; 3,2,1]。所以你从 (0,0)=9 开始,去 (0,1)=6,然后选择是 (0,2)=3 或 (1,1)=4。然而,这将 错过了 (1,0)=6 ,它比两者都大。所以你不能只看 两个邻居,但你必须原路返回。

【问题讨论】:

  • n 的范围为 (0..m^2),因此我认为您不能声称任何 O(f(n)) 都与矩阵的大小无关。
  • 生成的矩阵称为两个向量的外积。
  • 您的列表值的范围是多少?如果在实践中范围比列表的大小小很多,那么作为范围大小函数的算法可能比作为列表大小函数的算法更好。
  • 看Kth sum的类似问题:stackoverflow.com/questions/5212037/…
  • 您的 A 和 B 样本均已排序。我们是否应该假设它们总是排序的?

标签: performance algorithm search sorting language-agnostic


【解决方案1】:

我认为可以在O(n log n + n log m) 中完成。这是我的算法的草图,我认为会起作用。有点粗糙。

  1. 按降序排列 A。 (接受O(m log m)
  2. B 降序排序。 (接受O(m log m)
  3. s 成为min(m, n)。 (接受O(1)
  4. 创建s惰性序列迭代器L[0]L[s-1]L[i] 将遍历 sA[i]*B[0], A[i]*B[1], ..., A[i]*B[s-1]。 (接受O(s)
  5. 将迭代器放入优先级队列q。迭代器将根据其当前值进行优先级排序。 (需要O(s),因为最初它们已经按顺序排列)
  6. q 拉取n 值。拉出的最后一个值将是所需的结果。当一个迭代器被拉出时,它被重新插入q,使用它的下一个值作为新的优先级。如果迭代器已经用完,不要重新插入它。 (接受O(n log s)

总的来说,该算法将采用O(m log m + (s + n)log s),但s 等于mn

【讨论】:

    【解决方案2】:

    我认为没有O(f(n))的算法,它独立于m。

    但是有一个相对快速的 O(n*logm) 算法:

    首先,我们对两个数组进行排序,得到 A[0] > A[1] > ... > A[m-1] 和 B[0] > B[1] > ... > B [米-1]。 (当然,这是 O(mlogm)。)

    然后我们建立一个最大堆,它的元素是 A[0]*B[0], A[0]*B[1], ... A[0]*B[m-1]。我们维护一个“指针数组”P[0], P[1], ... P[m-1]。 P[i]=x 表示 B[i]*A[x] 当前在堆中。所有的 P[i] 最初都是零。

    在每次迭代中,我们从堆中弹出最大元素,这是下一个最大的产品。假设它来自B[i]*A[P[i]](我们可以记录堆中的元素来自哪个B[i]),然后我们将对应的指针向前移动:P[i] += 1,并将新的 B[i] * A[P[i]] 推入堆中。 (如果 P[i] 移动到超出范围 (>=m),我们只需将 -inf 推入堆中。)

    第 n 次迭代后,我们得到第 n 个最大的乘积。

    有n次迭代,每次都是O(logm)。

    编辑:添加一些细节

    【讨论】:

    • 我认为你可以认为 O(n*logm) 为 O(m^2*logm),这相当于只是对整个产品集进行排序。
    • @mbeckish 从理论上讲,您是对的。但如果 n 不是 \Theta(m^2),我的解决方案会更快。所以我认为至少在某些情况下它是有价值的。
    • 是的,对于给定的 m,并不是每个 n 值都会采取相同数量的步骤来求解。但是当谈到 big-O 时,它仍然被认为是 O(m^2*logm)。我认为 OP 正在寻求击败任何 n 的 O(m^2*logm)。
    【解决方案3】:

    您无需对 500 000 个元素进行排序即可获得前 3 个。

    只需取前 3 个,将它们放入 SortedList 中,然后遍历列表,将 3 个元素中的最小元素替换为新值(如果新值更高),然后使用结果列表。

    对两个列表都这样做,你会得到一个 3*3 的矩阵,它应该很容易取第三个值。

    Here is an implementation in scala.

    如果我们假设n小于m,并且A=[1, 3, 4] and B=[2, 2, 5],n=2:

    您将采用 (3, 4) => 对它们进行排序 (4,3)
    然后取 (2,5) => 对它们进行排序 (5, 2)

    您现在可以进行压缩搜索。当然现在最大的产品是(5, 4)。但下一个是 (4*2) 或 (5*3)。对于更长的列表,您可以记住 4*2 的结果是什么,仅将其与下一个产品进行比较,反之亦然。这样一来,您只会过多地计算一种产品。

    【讨论】:

    • 但我并不总是需要第三个。它可以是从 1 到 m^2 的任何值。如果是在下半场,我可以反转排序并找到 (m^2 - n)-th 最小的。所以最坏的情况是得到 (250,000)^2 元素,这是很多。
    • 搜索 5x5 而不是 10x10 仍然是一个很大的改进。 (2n*2n) 始终为 4n²,与 n*n=n² 相比 - 至少提高了 75%。如果 n 比 m 更小,则改进更大:(3n*3n)=9n² 以此类推。
    • 仍然是 O(m^2),这还远远不够好。 O(m log m) 对于初始排序是可以接受的,之后它需要变得更聪明。
    • @Timmy:我还没有完全弄清楚递归算法的作用,但他最初对 A 和 B 进行排序,而我只会对 min (N, M-N) 排序,在最坏的情况下,A/2, B/2.
    猜你喜欢
    • 2014-02-27
    • 1970-01-01
    • 2016-06-25
    • 2018-10-21
    • 1970-01-01
    • 2020-10-17
    • 2015-06-24
    • 1970-01-01
    • 2013-12-31
    相关资源
    最近更新 更多