【问题标题】:calculating the number of “inversions” in a permutation计算排列中“反转”的数量
【发布时间】:2011-06-29 16:05:25
【问题描述】:

令 A 为大小为 N 的数组。 如果i < jA[i] > A[j] ,我们将两个索引(i,j) 称为“逆”

我需要找到一个算法来接收大小为N(具有唯一数字)的数组,并在O(n*log(n)) 的时间内返回逆数。

【问题讨论】:

标签: arrays algorithm complexity-theory


【解决方案1】:

您可以使用merge sort 算法。

在合并算法的循环中,左右两半都是按升序排序的,我们希望将它们合并成一个单独的排序数组。请注意,右侧的所有元素的索引都高于左侧的索引。

假设 array[leftIndex] > array[rightIndex]。这意味着在索引为 leftIndex 的元素之后的左侧的所有元素也都大于右侧的当前元素(因为左侧是按升序排序的)。因此右侧的当前元素会生成 numberOfElementsInTheLeftSide - leftIndex + 1 个反转,因此将其添加到您的全局反转计数中。

一旦算法完成执行,您就会得到答案,在最坏的情况下,合并排序是 O(n log n)

【讨论】:

    【解决方案2】:

    Cham 和 Patrascu 于 2010 年在 SIAM 上发表了一篇题为 Counting Inversions, Offline Orthogonal Range Counting, and Related Problems 的文章,该文章给出了一个耗时 O(n sqrt(log(n))) 的算法。这是目前最知名的算法,并改进了长期存在的 O(n log(n) / log(log(n))) 算法。摘自:

    我们给出一个O(n sqrt(lg n))-时间算法 用于计算反转次数 在 n 个元素的排列中。这 改善了一个长期存在的先前 O(n lg n / lg lg n) 的界限 遵循Dietz的数据结构 [WADS'89],并回答以下问题 安德森和彼得森 [SODA'95]。作为 众所周知,Dietz 的结果是最优的 对于相关的动态排名问题, 我们的结果表明显着 离线设置的改进。

    我们的新技术非常简单:我们 执行一个“垂直分区” trie(类似于 van Emde Boas 树), 并使用来自外部记忆的想法。 然而,该技术发现许多 应用:例如,我们获得

    • d 维度中,一种算法 回答 n 离线正交范围 及时计数查询O(n lgd-2+1/d n);
    • 改进的 在线数据的构建时间 正交范围的结构 数数;
    • 改进的更新时间 对于部分和问题;
    • 更快 用于查找的字 RAM 算法 在排列的最大深度 轴对齐的矩形,对于 坡度选择问题。

    作为奖励, 我们也给出一个简单的 (1 + ε)-近似算法 计算运行的反转 线性时间,改进了以前的 O(n lg lg n) 由 Andersson 和 彼得森。

    【讨论】:

      【解决方案3】:

      我认为最棒的方法是使用binary indexed tree(这只是因为我喜欢数据结构)。请注意,如果您只需要一个解决方案,那么合并排序也可以正常工作(我只是认为这个概念完全摇滚!)。基本思想是:构建一个数据结构,更新 O(log n) 中的值并回答查询“到目前为止,数组中已经出现了多少小于 x 的数字?”鉴于此,您可以轻松地回答有多少大于 x,这有助于以 x 作为该对中的第二个数字的反转。例如,考虑列表 {3, 4, 1, 2}。

      在处理 3 时,到目前为止还没有其他数字,所以右侧有 3 的反演 = 0 在处理 4 时,到目前为止小于 4 的数字的数量 = 1,因此更大的数字(以及因此反转)的数量 = 0 现在,当处理 1 时,小于 1 的数字数量 = 0,这个更大的数字数量 = 2,这导致两个反转 (3,1) 和 (4,1)。同样的逻辑也适用于 2,它发现比它少 1 个数,因此比它大 2 个。

      现在,唯一的问题是了解这些更新和查询是如何在日志 n 中发生的。上面提到的 url 是我读过的关于这个主题的最好的教程之一。

      【讨论】:

        【解决方案4】:

        这些是原始的 MERGE 和 MERGE-SORT 算法 来自 Cormen、Leiserson、Rivest、Stein 算法简介:

        MERGE(A,p,q,r)
         1  n1 = q - p + 1
         2  n2 = r - q
         3  let L[1..n1 + 1] and R[1..n2 + 1] be new arrays
         4  for i = 1 to n1
         5      L[i] = A[p + i - 1]
         6  for j = 1 to n2
         7      R[j] = A[q + j]
         8  L[n1 + 1] = infinity
         9  R[n2 + 1] = infinity
        10  i = 1
        11  j = 1
        12  for k = p to r
        13      if L[i] <= R[j] 
        14          A[k] = L[i]
        15          i = i + 1
        16      else A[k] = R[j]
        17          j = j + 1
        

        MERGE-SORT(A,p,r)
         1 if p < r
         2     q = floor((p + r)/2)
         3     MERGE-SORT(A,p,q)
         4     MERGE-SORT(A,q + 1,r)
         5     MERGE(A,p,q,r)
        

        在 MERGE infinity 的第 8 行和第 9 行是所谓的哨兵卡, 它的值使得所有数组元素都小于它。 要获得反转的数量,可以引入一个全局计数器, 假设 ninv 在调用 MERGE-SORT 之前初始化为零 而不是通过添加一行来修改 MERGE 算法 在第 16 行之后的 else 语句中,类似于

        ninv += n1 - i
        

        比在 MERGE-SORT 完成后 ninv 会保存倒数的次数

        【讨论】:

          猜你喜欢
          • 2013-11-08
          • 1970-01-01
          • 2010-09-25
          • 1970-01-01
          • 1970-01-01
          • 2013-07-12
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多