Эффективно определить четность перестановки

у меня есть 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;
}