6

Suppose I have an array of integers int a[] = {0, 1, ... N-1}, where N is the size of a. Now I need to generate all permutations of a s that a[i] != i for all 0 <= i < N. How would you do that?

Michael
  • 41,026
  • 70
  • 193
  • 341

6 Answers6

8

Here's some C++ implementing an algorithm based on a bijective proof of the recurrence

!n = (n-1) * (!(n-1) + !(n-2)),

where !n is the number of derangements of n items.

#include <algorithm>
#include <ctime>
#include <iostream>
#include <vector>

static const int N = 12;
static int count;

template<class RAI>
void derange(RAI p, RAI a, RAI b, int n) {
    if (n < 2) {
        if (n == 0) {
            for (int i = 0; i < N; ++i) p[b[i]] = a[i];
            if (false) {
                for (int i = 0; i < N; ++i) std::cout << ' ' << p[i];
                std::cout << '\n';
            } else {
                ++count;
            }
        }
        return;
    }
    for (int i = 0; i < n - 1; ++i) {
        std::swap(a[i], a[n - 1]);
        derange(p, a, b, n - 1);
        std::swap(a[i], a[n - 1]);
        int j = b[i];
        b[i] = b[n - 2];
        b[n - 2] = b[n - 1];
        b[n - 1] = j;
        std::swap(a[i], a[n - 2]);
        derange(p, a, b, n - 2);
        std::swap(a[i], a[n - 2]);
        j = b[n - 1];
        b[n - 1] = b[n - 2];
        b[n - 2] = b[i];
        b[i] = j;
    }
}

int main() {
    std::vector<int> p(N);
    clock_t begin = clock();
    std::vector<int> a(N);
    std::vector<int> b(N);
    for (int i = 0; i < N; ++i) a[i] = b[i] = i;
    derange(p.begin(), a.begin(), b.begin(), N);
    std::cout << count << " permutations in " << clock() - begin << " clocks for derange()\n";
    count = 0;
    begin = clock();
    for (int i = 0; i < N; ++i) p[i] = i;
    while (std::next_permutation(p.begin(), p.end())) {
        for (int i = 0; i < N; ++i) {
            if (p[i] == i) goto bad;
        }
        ++count;
    bad:
        ;
    }
    std::cout << count << " permutations in " << clock() - begin << " clocks for next_permutation()\n";
}

On my machine, I get

176214841 permutations in 13741305 clocks for derange()
176214841 permutations in 14106430 clocks for next_permutation()

which IMHO is a wash. Probably there are improvements to be made on both sides (e.g., reimplement next_permutation with the derangement test that scans only the elements that changed); that's left as an exercise to the reader.

Per
  • 2,594
  • 12
  • 18
  • Also, `derange()` doesn't generate permutations in lex order. I don't know if that's problematic for you. – Per Dec 03 '11 at 21:04
  • Sir, how you devised this recurrence relation. Actually i am interested in the method to devise this recurrence relation. So if you can provide me some help it would be greatly helpful. – Akashdeep Saluja Nov 03 '12 at 12:20
  • I wasn't Per who derived it, but Euler I guess. See here: https://oeis.org/wiki/Number_of_derangements - take all permutations (factorial) and subtract all permutations which do have fixed points ;). Also note: !0 = 1, !1 = 0, !2 = 1 etc. – Tomasz Gandor Oct 01 '19 at 09:12
5

If you have access to C++ STL, use next_permutation, and do an additional check of a[i] != i in a do-while loop.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 8
    Note: this algorithm may look inefficient, but in fact, the fraction of derangements (= permutations where no element is left in its original position) converges quickly in N to exp(-1), so it's actually asymptotically optimal (assuming that the whole of each permutation is consumed). – Per Dec 03 '11 at 18:46
  • @Per You are right: since the OP is looking to enumerate all permutations anyway, and because roughly one in three permutations is a derangement, the algorithm is not going to be terribly inefficient. Considering the savings in coding time and debugging, I's say it should be acceptable. Thanks for the note! – Sergey Kalinichenko Dec 03 '11 at 18:53
5

If you want to avoid the filter approach that others have suggested (generate the permutations in lexicographic order and skip those with fixed points), then you should generate them based on cycle notation rather than one-line notation (discussion of notation).

The cycle-type of a permutation of n is a partition of n, that is a weakly decreasing sequence of positive integers that sums to n. The condition that a permutation has no fixed points is equivalent to its cycle-type having no 1s. For example, if n=5, then the possible cycle-types are

5
4,1
3,2
3,1,1
2,2,1
2,1,1,1
1,1,1,1,1

Of those, only 5 and 3,2 are valid for this problem since all others contain a 1. Therefore the strategy is to generate partitions with smallest part at least 2, then for each such partition, generate all permutations with that cycle-type.

PengOne
  • 48,188
  • 17
  • 130
  • 149
  • [related article](http://mathforum.org/library/drmath/view/61957.html) discussing same approach (i.e derangement as a permutatiion which consists of non-trivial cycle factors) – Nikos M. May 30 '15 at 15:21
2

The permutations you are looking for are called derangements. As others have observed, uniformly randomly distributed derangements can be generated by generating uniformly randomly distributed permutations and then rejecting permutations that have fixed points (where a[i] == i). The rejection method runs in time e*n + o(n) where e is Euler's constant 2.71828... . An alternative algorithm similar to @Per's runs in time 2*n + O(log^2 n). However, the fastest algorithm I've been able to find, an early rejection algorithm, runs in time (e-1)*(n-1). Instead of waiting for the permutation to be generated and then rejecting it (or not), the permutation is tested for fixed points while it is being constructed, allowing for rejection at the earliest possible moment. Here's my implementation of the early rejection method for derangements in Java.

  public static int[] randomDerangement(int n)
    throws IllegalArgumentException {

    if (n<2)
      throw new IllegalArgumentException("argument must be >= 2 but was " + n);

    int[] result = new int[n];
    boolean found = false;

    while (!found) {
      for (int i=0; i<n; i++) result[i] = i;
      boolean fixed = false;
      for (int i=n-1; i>=0; i--) {
        int j = rand.nextInt(i+1);
        if (i == result[j]) {
          fixed = true;
          break;
        }
        else {
          int temp = result[i];
          result[i] = result[j];
          result[j] = temp;
        }
     }
      if (!fixed) found = true;
    }

    return result;
  }

For an alternative approach, see my post at Shuffle list, ensuring that no item remains in same position.

Community
  • 1
  • 1
Edward Doolittle
  • 4,002
  • 2
  • 14
  • 27
1

Just a hunch: I think lexicographic permutation might be possible to modify to solve this.

Re-arrange the array 1,2,3,4,5,6,... by swapping pairs of odd and even elements into 2,1,4,3,6,5,... to construct the permutation with lowest lexicographic order. Then use the standard algorithm, with the additional constraint that you cannot swap element i into position i.

If the array has an odd number of elements, you will have to make another swap at the end to ensure that element N-1 is not in position N-1.

Anders Lindahl
  • 41,582
  • 9
  • 89
  • 93
1

Here's a small recursive approach in python:

def perm(array,permutation = [], i = 1):
    if len(array) > 0 :
        for element in array:
            if element != i:
                newarray = list(array)
                newarray.remove(element)

                newpermutation = list(permutation)
                newpermutation.append(element)

                perm(newarray,newpermutation,i+1)
    else:
        print permutation

Running perm(range(1,5)) will give the following output:

[2, 1, 4, 3]
[2, 3, 4, 1]
[2, 4, 1, 3]
[3, 1, 4, 2]
[3, 4, 1, 2]
[3, 4, 2, 1]
[4, 1, 2, 3]
[4, 3, 1, 2]
[4, 3, 2, 1]
Anders Lindahl
  • 41,582
  • 9
  • 89
  • 93