Generating permutations lazily

I'm looking for an algorithm to generate permutations of a set in such a way that I could make a lazy list of them in Clojure. ie I'd like to iterate over a list of permutations where each permutation is not calculated until I request it, and all of the permutations don't have to be stored in memory at once.

Alternatively I'm looking for an algorithm where given a certain set, it will return the "next" permutation of that set, in such a way that repeatedly calling the function on its own output will cycle through all permutations of the original set, in some order (what the order is doesn't matter).

Is there such an algorithm? Most of the permutation-generating algorithms I've seen tend to generate them all at once (usually recursively), which doesn't scale to very large sets. An implementation in Clojure (or another functional language) would be helpful but I can figure it out from pseudocode.


Yes, there is a "next permutation" algorithm, and it's quite simple too. The C++ standard template library (STL) even has a function called next_permutation .

The algorithm actually finds the next permutation -- the lexicographically next one. The idea is this: suppose you are given a sequence, say "32541". What is the next permutation?

If you think about it, you'll see that it is "34125". And your thoughts were probably something this: In "32541",

  • there is no way to keep the "32" fixed and find a later permutation in the "541" part, because that permutation is already the last one for 5,4, and 1 -- it is sorted in decreasing order.
  • So you'll have to change the "2" to something bigger -- in fact, to the smallest number bigger than it in the "541" part, namely 4.
  • Now, once you've decided that the permutation will start as "34", the rest of the numbers should be in increasing order, so the answer is "34125".
  • The algorithm is to implement precisely that line of reasoning:

  • Find the longest "tail" that is ordered in decreasing order. (The "541" part.)
  • Change the number just before the tail (the "2") to the smallest number bigger than it in the tail (the 4).
  • Sort the tail in increasing order.
  • You can do (1.) efficiently by starting at the end and going backwards as long as the previous element is not smaller than the current element. You can do (2.) by just swapping the "4" with the '2", so you'll have "34521". Once you do this, you can avoid using a sorting algorithm for (3.), because the tail was, and is still (think about this), sorted in decreasing order, so it only needs to be reversed.

    The C++ code does precisely this (look at the source in /usr/include/c++/4.0.0/bits/stl_algo.h on your system, or see this article); it should be simple to translate it to your language: [Read "BidirectionalIterator" as "pointer", if you're unfamiliar with C++ iterators. The code returns false if there is no next permutation, ie we are already in decreasing order.]

    template <class BidirectionalIterator>
    bool next_permutation(BidirectionalIterator first,
                          BidirectionalIterator last) {
        if (first == last) return false;
        BidirectionalIterator i = first;
        ++i;
        if (i == last) return false;
        i = last;
        --i;
        for(;;) {
            BidirectionalIterator ii = i--;
            if (*i <*ii) {
                BidirectionalIterator j = last;
                while (!(*i <*--j));
                iter_swap(i, j);
                reverse(ii, last);
                return true;
            }
            if (i == first) {
                reverse(first, last);
                return false;
            }
        }
    }
    

    It might seem that it can take O(n) time per permutation, but if you think about it more carefully, you can prove that it takes O(n!) time for all permutations in total, so only O(1) -- constant time -- per permutation.

    The good thing is that the algorithm works even when you have a sequence with repeated elements: with, say, "232254421", it would find the tail as "54421", swap the "2" and "4" (so "232454221"), reverse the rest, giving "232412245", which is the next permutation.


    Assuming that we're talking about lexicographic order over the values being permuted, there are two general approaches that you can use:

  • transform one permutation of the elements to the next permutation (as ShreevatsaR posted), or
  • directly compute the n th permutation, while counting n from 0 upward.
  • For those (like me ;-) who don't speak c++ as natives, approach 1 can be implemented from the following pseudo-code, assuming zero-based indexing of an array with index zero on the "left" (substituting some other structure, such as a list, is "left as an exercise" ;-):

    1. scan the array from right-to-left (indices descending from N-1 to 0)
    1.1. if the current element is less than its right-hand neighbor,
         call the current element the pivot,
         and stop scanning
    1.2. if the left end is reached without finding a pivot,
         reverse the array and return
         (the permutation was the lexicographically last, so its time to start over)
    2. scan the array from right-to-left again,
       to find the rightmost element larger than the pivot
       (call that one the successor)
    3. swap the pivot and the successor
    4. reverse the portion of the array to the right of where the pivot was found
    5. return
    

    Here's an example starting with a current permutation of CADB:

    1. scanning from the right finds A as the pivot in position 1
    2. scanning again finds B as the successor in position 3
    3. swapping pivot and successor gives CBDA
    4. reversing everything following position 1 (i.e. positions 2..3) gives CBAD
    5. CBAD is the next permutation after CADB
    

    For the second approach (direct computation of the n th permutation), remember that there are N! permutations of N elements. Therefore, if you are permuting N elements, the first (N-1)! permutations must begin with the smallest element, the next (N-1)! permutations must begin with the second smallest, and so on. This leads to the following recursive approach (again in pseudo-code, numbering the permutations and positions from 0):

    To find permutation x of array A, where A has N elements:
    0. if A has one element, return it
    1. set p to ( x / (N-1)! ) mod N
    2. the desired permutation will be A[p] followed by
       permutation ( x mod (N-1)! )
       of the elements remaining in A after position p is removed
    

    So, for example, the 13th permutation of ABCD is found as follows:

    perm 13 of ABCD: {p = (13 / 3!) mod 4 = (13 / 6) mod 4 = 2; ABCD[2] = C}
    C followed by perm 1 of ABD {because 13 mod 3! = 13 mod 6 = 1}
      perm 1 of ABD: {p = (1 / 2!) mod 3 = (1 / 2) mod 2 = 0; ABD[0] = A}
      A followed by perm 1 of BD {because 1 mod 2! = 1 mod 2 = 1}
        perm 1 of BD: {p = (1 / 1!) mod 2 = (1 / 1) mod 2 = 1; BD[1] = D}
        D followed by perm 0 of B {because 1 mod 1! = 1 mod 1 = 0}
          B (because there's only one element)
        DB
      ADB
    CADB
    

    Incidentally, the "removal" of elements can be represented by a parallel array of booleans which indicates which elements are still available, so it is not necessary to create a new array on each recursive call.

    So, to iterate across the permutations of ABCD, just count from 0 to 23 (4!-1) and directly compute the corresponding permutation.


    You should check the Permutations article on wikipeda. Also, there is the concept of Factoradic numbers.

    Anyway, the mathematical problem is quite hard.

    In C# you can use an iterator , and stop the permutation algorithm using yield . The problem with this is that you cannot go back and forth, or use an index .

    链接地址: http://www.djcxy.com/p/37286.html

    上一篇: 将范围从1-5扩大到1-7

    下一篇: 懒洋洋地产生排列