【问题标题】:Efficiently determine the parity of a permutation有效地确定排列的奇偶性
【发布时间】:2013-12-20 11:36:13
【问题描述】:

我有一个长度为 Nint[] 数组,其中包含值 0、1、2、.... (N-1),即它表示整数索引的排列。

确定排列是奇数还是偶数parity 的最有效方法是什么?

(如果可能的话,我特别热衷于避免为临时工作空间分配对象......)

【问题讨论】:

标签: java algorithm permutation


【解决方案1】:

我认为您可以通过简单地计算 cycle decomposition 在 O(n) 时间和 O(n) 空间内完成此操作。

您可以通过简单地从第一个元素开始并沿着路径直到返回起点来计算 O(n) 中的循环分解。这为您提供了第一个周期。沿着路径将每个节点标记为已访问。

然后重复下一个未访问的节点,直到所有节点都标记为已访问。

长度为 k 的循环的奇偶性为 (k-1)%2,因此您可以简单地将您发现的所有循环的奇偶性相加,以找到整个排列的奇偶性。

节省空间

将节点标记为已访问的一种方法是在访问时将 N 添加到数组中的每个值。然后,您将能够进行最后的整理 O(n) 传递以将所有数字恢复为原始值。

【讨论】:

  • 谢谢,这种方法似乎是最好的,所以我把它标记为正确答案。我还在下面发布了一个答案,其中包括一些代码/额外的优化。
【解决方案2】:

我选择了 Peter de Rivaz 的答案作为正确答案,因为这是我最终使用的算法方法。

但是我使用了一些额外的优化,所以我想我会分享它们:

  • 先检查数据的大小
  • 如果大于64,使用java.util.BitSet来存储访问过的元素
  • 如果小于或等于 64,则使用 long 和按位运算来存储访问过的元素。这使得许多只使用小排列的应用程序的空间为 O(1)。
  • 实际上返回交换计数而不是奇偶校验。如果您需要,这可以为您提供平价,但可能对其他用途有用,并且计算成本不会更高。

代码如下:

public int swapCount() {
    if (length()<=64) {
        return swapCountSmall();
    } else {
        return swapCountLong();
    }
}

private int swapCountLong() {
    int n=length();
    int swaps=0;
    BitSet seen=new BitSet(n);
    for (int i=0; i<n; i++) {
        if (seen.get(i)) continue;
        seen.set(i);
        for(int j=data[i]; !seen.get(j); j=data[j]) {
            seen.set(j);
            swaps++;
        }       
    }
    return swaps;
}

private int swapCountSmall() {
    int n=length();
    int swaps=0;
    long seen=0;
    for (int i=0; i<n; i++) {
        long mask=(1L<<i);
        if ((seen&mask)!=0) continue;
        seen|=mask;
        for(int j=data[i]; (seen&(1L<<j))==0; j=data[j]) {
            seen|=(1L<<j);
            swaps++;
        }       
    }
    return swaps;
}

【讨论】:

    【解决方案3】:

    您想要反转次数的奇偶性。您可以使用归并排序在 O(n * log n) 时间内完成此操作,但要么丢失初始数组,要么需要 O(n) 量级的额外内存。

    一个使用O(n)额外空间并且是O(n * log n)的简单算法:

    inv = 0
    mergesort A into a copy B
    for i from 1 to length(A):
        binary search for position j of A[i] in B
        remove B[j] from B
        inv = inv + (j - 1)
    

    也就是说,我认为在亚线性内存中是不可能的。另见:

    【讨论】:

      【解决方案4】:

      考虑这种方法...

      从排列中得到逆排列,通过交换行和 根据顶行顺序排序。这是 O(nlogn)

      然后,模拟执行逆排列并计算交换次数,时间为 O(n)。根据这个,这应该给出排列的奇偶性

      一个偶排列可以得到一个偶数的组合 数量且只有偶数次交换(称为换位) 两个元素,而奇数排列可以通过(仅)奇数 换位次数。

      来自维基百科。

      这是我之前的一些代码,它执行逆排列,我只是对其进行了一些修改以计算交换,您可以删除所有提及 ap 包含逆排列。

      size_t
      permute_inverse (std::vector<int> &a, std::vector<size_t> &p) {
          size_t cnt = 0
          for (size_t i = 0; i < a.size(); ++i) {
              while (i != p[i]) {
                  ++cnt;
                  std::swap (a[i], a[p[i]]);
                  std::swap (p[i], p[p[i]]);
              }
          }
          return cnt;
      }
      

      【讨论】:

        猜你喜欢
        • 2018-06-03
        • 1970-01-01
        • 2013-06-09
        • 2010-12-02
        • 1970-01-01
        • 1970-01-01
        • 2016-06-13
        • 2018-10-07
        • 1970-01-01
        相关资源
        最近更新 更多