Эффективно определить четность перестановки
у меня есть int[]
массив длины N
содержит значения 0, 1, 2,.... (N-1), т. е. он представляет собой перестановку целых индексов.
каков наиболее эффективный способ определить, имеет ли перестановка нечетное или четное паритет?
(Я особенно стараюсь избегать выделения объектов для временного рабочего пространства, если это возможно....)
4 ответов
Я думаю, вы можете сделать это в O(n) времени и o (n) пространстве, просто вычисляя разложение цикла.
вы можете вычислить разложение цикла в O(n), просто начав с первого элемента и следуя пути, пока не вернетесь к началу. Это дает вам первый цикл. Отметьте каждый узел как посещенный, следуя по пути.
затем повторите для следующего непрошеного узла, пока все узлы не будут отмечены как посещенные.
четность a цикл длины k равен (k-1)%2, поэтому вы можете просто сложить паритеты всех обнаруженных циклов, чтобы найти четность общей перестановки.
экономия пространства
один из способов пометить узлы как посещаемые - добавить N к каждому значению в массиве, когда он посещается. Затем вы сможете сделать окончательный проход очистки O(n), чтобы вернуть все числа к исходным значениям.
рассмотреть этот подход...
из перестановки получите обратную перестановку, поменяв местами строки и сортировка по порядку верхних строк. Это 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;
}
вы хотите четность числа инверсий. Вы можете сделать это за 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)
тем не менее, я не думаю, что это возможно сделать в сублинейной памяти. Видеть также:
Я выбрал ответ Питера де Риваза как правильный ответ, так как это был алгоритмический подход, который я использовал.
однако я использовал пару дополнительных оптимизаций, поэтому я думал, что поделюсь ими:
- сначала проверьте размер данных
- если он больше 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;
}