【问题标题】:Why is iterative k-way merge O(nk^2)?为什么迭代 k 路合并 O(nk^2)?
【发布时间】:2012-06-14 03:21:28
【问题描述】:

k-way merge 是将 k 个排序数组作为输入的算法,每个数组的大小为 n。它输出所有元素的单个排序数组。

它使用合并排序算法的核心“合并”例程将数组 1 合并到数组 2,然后将数组 3 合并到这个合并的数组,以此类推,直到所有 k 个数组都合并。

我原以为这个算法是 O(kn),因为该算法遍历了每个 k 个数组(每个数组的长度为 n)一次。为什么是 O(nk^2)?

【问题讨论】:

  • 您可以通过使用堆或选择树在每个阶段从 k 个可能的选择中选择下一个元素来获得 O(n k log k)。例如。 Knuth Volume II 排序和搜索第 5.4.1 节
  • 算法选择数组对,所以你有 comb(k 2) = k * (k-1) /2。由于每个数组的大小为 n 并且合并需要 O(n) 你得到 O(nk^2)
  • 你可以使用队列,时间为 O(n log k) 其中 n 是整数个数,k 是排序数组的个数
  • @Dejel 遍历所有元素将花费 O(nk)。使用堆,我们可以得到 O(nklogk)。能否详细说明如何实现O(nlogk)?

标签: algorithm


【解决方案1】:

因为它不会遍历每个 k 数组一次。第一个数组被遍历 k-1 次,第一个为 merge(array-1,array-2),第二个为 merge(merge(array-1, array-2), array-3) ... 以此类推.

结果是 k-1 合并,平均大小为 n*(k+1)/2,复杂度为 O(n*(k^2-1)/2),即 O(nk^2) .

您犯的错误是忘记了合并是串行而不是并行完成的,因此数组的大小不都是 n。

【讨论】:

  • 你是怎么得到平均大小的?
  • 第一次合并的大小为 2n(两个大小为 n 的数组)。第二个大小为 3n(累积的数组为 2n,大小为 n)。您应该能够看到第 k 遍是 (k+1)n。平均大小大约等于一半或 n(k+1)/2,任何残差项都不会影响 O() 分析。
【解决方案2】:

实际上在最坏的情况下,第一个数组会有 n 次比较,第二个是 2n,第三个是 3n很快直到 (k - 1)n。
所以现在复杂性变得简单了

n + 2n + 3n + 4n + ... + (k - 1)n
= n(1 + 2 + 3 + 4 + ... + (k - 1))
= n((k - 1)*k) / 2
= n(k^2 - k) / 2
= O(nk ^ 2)

:-)

【讨论】:

  • 我知道这是一个无知的问题,但你能告诉我你是怎么知道从第 2 步到第 3 步的吗?换句话说,为什么 (1...k-1) 的和等于 ((k-1)*k)/2?这是人们所说的高斯添加一长串数字的技巧的形式化吗?
  • @user3208862 : 这是因为求和规则,当你计算 (1+2+3+...k) 它等于 k(k+1)/2 检查en.wikipedia.org/wiki/Summation 以供参考跨度>
【解决方案3】:

这个怎么样:

第 1 步: 合并数组(1 和 2)、数组(3 和 4)等等。 (k/2 数组合并 2n,总工作量 kn)。

第 2 步: 合并数组(1,2 和 3,4),数组(5,6 和 7,8)等等(4n 的 k/4 次合并,总工作量 kn)。

第 3 步: 重复...

会有 log(k) 个这样的“步骤”,每个都有 kn 个工作。因此完成的总工作量 = O(k.n.log(k))。

即便如此,如果我们只是对数组的所有元素进行排序,我们仍然可以在 O(k.n.log(k.n)) 时间内合并所有元素。

【讨论】:

  • 老实说,我认为这种自下而上的合并比 OP 问题中的合并要好得多
【解决方案4】:

k-way merge 是将 k 个排序数组作为输入的算法,每个数组的大小为 n。它输出所有元素的单个排序数组。

我还以为这个算法是O(kn)

我们可以通过矛盾来反驳这一点。为 m 项使用您的算法定义一个排序算法,其中 k=m 和 n=1。根据假设,排序算法在 O(m) 时间内成功。矛盾的是,众所周知,任何排序算法的最坏情况至少为 O(m log m)。

【讨论】:

  • 以防万一有人在这里绊倒,nlogn 的最坏情况排序时间仅适用于基于 COMPARISON 的排序算法。计数或桶排序等算法不是比较排序,仅在满足输入的某些限制时才适用。例如。快速查看计数排序会告诉您,如果任何元素的值(例如 100)大于数组大小,它将超出范围。
【解决方案5】:

您不必每次都一个一个地比较项目。 您应该简单地维护排序集中最近的 K 个项目。 您删除最小的并用下一个元素替换它。这应该是 n.log(k)

相关article。免责声明:我参与编写了它

【讨论】:

  • 正确。但是你需要提到你正在使用一个堆(例如队列)
  • 这就是我在回答中所说的“排序集”的意思。实际上你需要一个排序的多重集。堆是一种可能的实现方式。
  • 对于你的堆,你最终会在你的结构中插入 nk-1 个元素。由于插入成本为 O(log(k)),因此整个合并的复杂性应该是 O(nklog(k)),而不是您所说的 nlog(k)。如问题所述,n 是块的大小。如果我们认为 N 是总大小,我们有 N=nk 因此关于 N 的复杂度是 O(Nlog(k))
【解决方案6】:

1) 你有 k 个已排序的数组,每个数组的大小为 n。因此总元素数 = k * n

2)取所有k个数组的第一个元素并创建一个序列。然后找到这个序列的最小值。该最小值存储在输出数组中。找到 k 个元素的最小值的比较次数为 k - 1。

3) 因此比较的总数
=(比较/元素)*元素数量
= (k - 1) * k * n
= k^2 * n // 大约

【讨论】:

    【解决方案7】:

    一个常见的实现为 k 个排序数组 {i_1, i_2, i__k} 中的每一个保留一个索引数组。在每次迭代中,算法从所有 k 个数组中找到最小的下一个元素并将其存储在输出数组中。由于您正在进行 kn 次迭代并每次迭代扫描 k 个数组,因此总复杂度为 O(k^2 * n)。

    这是一些伪代码:

    Input: A[j] j = 1..k : k sorted arrays each of length n
    Output: B : Sorted array of length kn
    
    // Initialize array of indexes
    I[j] = 0 for j = 1..k
    
    q = 0
    
    while (q < kn):
        p = argmin({A[j][I[j]]}) j = 1..k           // Get the array for which the next unprocessed element is minimal (ignores arrays for which I[j] > n)
        B[q] = A[p][I[p]]
        I[p] = I[p] + 1
        q = q + 1
    

    【讨论】:

      【解决方案8】:
      1. 你有 k 个数组,每个数组有 n 个元素。这意味着总共 k*n 个元素。

      2. 将其视为一个 k*n 矩阵。要将第一个元素添加到合并/最终数组中,您需要比较 k 个数组的头。这意味着对于最终数组中的一个元素,您需要进行 k 次比较。

      所以从1到2,对于Kn个元素,总耗时是O(kk*n)。

      【讨论】:

        【解决方案9】:

        对于那些想了解细节或需要帮助的人,我将扩展 Recurse 的 answer 和后续评论

        • 我们只需要 k-1 合并,因为最后一个数组没有与任何东西合并
        • 算术数列各项求和的公式很有帮助; Sn=n(a1 + an)2

        逐步完成 k 数组与 n 元素的前 4 次合并

        +-------+-------------------+-------------+
        | Merge | Size of new array |    Note     |
        +-------+-------------------+-------------+
        | 1     | n+n  = 2n         | first merge  |
        | 2     | 2n+n = 3n         |             |
        | 3     | 3n+n = 4n         |             |
        | 4     | 4n+n = 5n         |             |
        | k-1   | (k-1)n+n = kn     | last merge  |
        +-------+-------------------+-------------+
        

        要找到平均大小,我们需要将所有大小相加并除以合并次数 (k-1)。使用公式对第一个 n 项求和,Sn=n(a1 + an)2,我们只需要 first 和 last 项:

        • a1=2n(第一学期)
        • 一个=kn(上学期)

        我们想将所有术语相加,所以n=k-1(我们拥有的术语数)。代入数字,我们得到所有项之和的公式

        Sn = ( (k-1)(2n+kn) )/2

        但是,要找到 平均 大小,我们必须除以词条数 (k-1)。这抵消了分子中的k-1,我们的平均大小为

        (2n + kn)/2

        现在我们有了平均大小,我们可以将它乘以合并次数,即k-1。为了使乘法更容易,忽略/2,只乘分子:

          (k-1)(2n+kn)
        = (k^2)n + kn - 2n
        

        此时您可以重新引入/2,但应该没有任何必要,因为很明显主要术语是(k^2)*n

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-01-13
          • 1970-01-01
          • 2011-05-06
          • 2020-12-16
          • 1970-01-01
          • 2012-10-13
          • 2017-09-26
          相关资源
          最近更新 更多