Stepping through all permutations one swap at a time

前端 未结 4 1704
你的背包
你的背包 2020-12-10 05:19

Given a list of n distinct items, how can I step through each permutation of the items swapping just one pair of values at a time? (I assume it is possible, it certainly fee

相关标签:
4条回答
  • 2020-12-10 05:47

    You could have a look at https://sourceforge.net/projects/swappermutation/ which is a Java implementation of exactly you requirements: an Iterator that generates Swaps. Created this some time ago an recently updated.

    0 讨论(0)
  • 2020-12-10 06:00

    I'm sure it's too late for you, but I found a nice addition to this question: Steinhaus–Johnson–Trotter algorithm and its variants do exactly what you asked for. Moreover it has the additional property that it always swaps adjacent indices. I tried to implement one of the variants (Even's) in Java as an iterator and works nicely:

    import java.util.*;
    
    // Based on https://en.wikipedia.org/wiki/Steinhaus%E2%80%93Johnson%E2%80%93Trotter_algorithm#Even.27s_speedup
    public class PermIterator
        implements Iterator<int[]>
    {
        private int[] next = null;
    
        private final int n;
        private int[] perm;
        private int[] dirs;
    
        public PermIterator(int size) {
            n = size;
            if (n <= 0) {
                perm = (dirs = null);
            } else {
                perm = new int[n];
                dirs = new int[n];
                for(int i = 0; i < n; i++) {
                    perm[i] = i;
                    dirs[i] = -1;
                }
                dirs[0] = 0;
            }
    
            next = perm;
        }
    
        @Override
        public int[] next() {
            int[] r = makeNext();
            next = null;
            return r;
        }
    
        @Override
        public boolean hasNext() {
            return (makeNext() != null);
        }
    
        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    
        private int[] makeNext() {
            if (next != null)
                return next;
            if (perm == null)
                return null;
    
            // find the largest element with != 0 direction
            int i = -1, e = -1;
            for(int j = 0; j < n; j++)
                if ((dirs[j] != 0) && (perm[j] > e)) {
                    e = perm[j];
                    i = j;
                }
    
            if (i == -1) // no such element -> no more premutations
                return (next = (perm = (dirs = null))); // no more permutations
    
            // swap with the element in its direction
            int k = i + dirs[i];
            swap(i, k, dirs);
            swap(i, k, perm);
            // if it's at the start/end or the next element in the direction
            // is greater, reset its direction.
            if ((k == 0) || (k == n-1) || (perm[k + dirs[k]] > e))
                dirs[k] = 0;
    
            // set directions to all greater elements
            for(int j = 0; j < n; j++)
                if (perm[j] > e)
                    dirs[j] = (j < k) ? +1 : -1;
    
            return (next = perm);
        }
    
        protected static void swap(int i, int j, int[] arr) {
            int v = arr[i];
            arr[i] = arr[j];
            arr[j] = v;
        }
    
    
        // -----------------------------------------------------------------
        // Testing code:
    
        public static void main(String argv[]) {
            String s = argv[0];
            for(Iterator<int[]> it = new PermIterator(s.length()); it.hasNext(); ) {
                print(s, it.next());
            }
        }
    
        protected static void print(String s, int[] perm) {
            for(int j = 0; j < perm.length; j++)
                System.out.print(s.charAt(perm[j]));
            System.out.println();
        }
    }
    

    It'd be easy to modify it to an infinite iterator that restarts the cycle at the end, or an iterator that would return the swapped indices instead of the next permutation.

    Here another link collecting various implementations.

    0 讨论(0)
  • 2020-12-10 06:07

    Have a look at the C++ standard library function next_permuation(...). That should be a good starting point.

    0 讨论(0)
  • 2020-12-10 06:08

    Ah, once I calculated a sequence for n=4 (with the "always swap the first item with another" constraint), I was able to find sequence A123400 in the OEIS, which told me I need "Ehrlich's swap method".

    Google found me a C++ implementation, which I assume from this is under the GPL. I've also found Knuth's fascicle 2b which describes various solutions to exactly my problem.

    Once I have a tested C implementation I'll update this with code.

    Here's some perl code that implements Ehrlich's method based on Knuth's description. For lists up to 10 items, I tested in each case that it correctly generated the complete list of permutations and then stopped.

    #
    # Given a count of items in a list, returns an iterator that yields the index
    # of the item with which the zeroth item should be swapped to generate a new
    # permutation. Returns undef when all permutations have been generated.
    #
    # Assumes all items are distinct; requires a positive integer for the count.
    #
    sub perm_iterator {
        my $n = shift;
        my @b = (0 .. $n - 1);
        my @c = (undef, (0) x $n);
        my $k;
        return sub {
            $k = 1;
            $c[$k++] = 0 while $c[$k] == $k;
            return undef if $k == $n;
            ++$c[$k];
            @b[1 .. $k - 1] = reverse @b[1 .. $k - 1];
            return $b[$k];
        };
    }
    

    Example use:

    #!/usr/bin/perl -w
    use strict;
    my @items = @ARGV;
    my $iterator = perm_iterator(scalar @items);
    print "Starting permutation: @items\n";
    while (my $swap = $iterator->()) {
        @items[0, $swap] = @items[$swap, 0];
        print "Next permutation: @items\n";
    }
    print "All permutations traversed.\n";
    exit 0;
    

    By request, python code. (Sorry, it probably isn't overly idiomatic. Suggestions for improvement welcomed.)

    class ehrlich_iter:
      def __init__(self, n):
        self.n = n
        self.b = range(0, n)
        self.c = [0] * (n + 1)
    
      def __iter__(self):
        return self
    
      def next(self):
        k = 1
        while self.c[k] == k:
          self.c[k] = 0
          k += 1
        if k == self.n:
          raise StopIteration
        self.c[k] += 1
        self.b[1:k - 1].reverse
        return self.b[k]
    
    mylist = [ 1, 2, 3, 4 ]   # test it
    print "Starting permutation: ", mylist
    for v in ehrlich_iter(len(mylist)):
      mylist[0], mylist[v] = mylist[v], mylist[0]
      print "Next permutation: ", mylist
    print "All permutations traversed."
    
    0 讨论(0)
提交回复
热议问题