How to generate permutations where a[i] != i?

后端 未结 6 530
野的像风
野的像风 2021-01-18 12:11

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

相关标签:
6条回答
  • 2021-01-18 12:52

    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.

    0 讨论(0)
  • 2021-01-18 12:53

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

    0 讨论(0)
  • 2021-01-18 12:55

    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.

    0 讨论(0)
  • 2021-01-18 12:58

    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.

    0 讨论(0)
  • 2021-01-18 12:59

    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.

    0 讨论(0)
  • 2021-01-18 13:02

    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]
    
    0 讨论(0)
提交回复
热议问题