我有一个int[]
长度为N
的数组,其中包含值0,1,2,....(N-1),即它表示整数索引的排列。
确定排列是奇数还是偶数parity的最有效方法是什么?
(我特别希望尽可能避免为临时工作空间分配对象......)
答案 0 :(得分:8)
我认为你可以通过简单地计算cycle decomposition在O(n)时间和O(n)空间中做到这一点。
您可以通过简单地从第一个元素开始并沿着路径计算O(n)中的循环分解,直到您返回到开始。这为您提供了第一个周期。在跟踪路径时将每个节点标记为已访问。
然后重复下一个未访问的节点,直到所有节点都标记为已访问。
长度k的周期的奇偶校验是(k-1)%2,因此您可以简单地将您发现的所有周期的奇偶校验加起来以找到整体排列的奇偶校验。
将节点标记为已访问的一种方法是在访问时将N添加到数组中的每个值。然后,您可以进行最后整理O(n)传递,将所有数字恢复为原始值。
答案 1 :(得分:3)
考虑这种方法......
从排列中,通过交换行和得到逆置换 根据顶行顺序排序。这是O(nlogn)
然后,模拟执行逆置换并计算交换,用于O(n)。根据这个
,这应该给出排列的奇偶性可以获得偶数排列作为偶数的组成 号码和偶数个交换(称为换位) 两个元素,而奇数排列可以通过(仅)奇数获得 换位次数。
来自维基百科。
这里有一些我曾经躺着的代码,它执行反向排列,我只是修改了一下来计算掉期,你可以删除所有提到的a
,p
包含逆置换。
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;
}
答案 2 :(得分:2)
您想要反转次数的奇偶校验。您可以使用合并排序在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)
那就是说,我认为不可能在次线性记忆中做到这一点。另见:
答案 3 :(得分:0)
我选择Peter de Rivaz作为正确答案的答案,因为这是我最终使用的算法方法。
但是我使用了一些额外的优化,所以我想我会分享它们:
java.util.BitSet
存储已访问的元素以下代码:
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;
}