Recall that there are n!
permutations of n
items. The n!
can be easily understood in the following way:
1. There are `n` options for choosing the first item.
2. There are `n-1` items remaining from which to choose the second item
...
n-1. There are `2` options left for the `n-1`-th item.
n. There is only 1 item left for the `n`-th position.
Thus there are (n) * (n-1) * (n-2) * ... (2) * (1) = n!
total choices for how to order the items.
This directly reveals a method for enumerating the permutations using a mixed-radix numbering scheme. In this scheme, the most-significant digit will be base n
, the next-most-significant digit will be base n-1
..etc.
You use such a mixed-radix number to select a permutation in the following way:
- Use the most significant digit to select an element from the array (note that the first digit ranges from
[0, n-1]
, and there are n
elements to select from, so you can use it as the index of the item to select.)
- Remove the selected element from the array, record that it's the first element of the permuted array, and compact the remaining elements to the front of the array.
- Use the second-most significant digit to select an element from the remaining items (note that the value of this digit ranges from
[0, n-2]
, and there are n-1 digits remaining)
- Remove the selected element recording it as the second element in the permuted array
- Repeat until all items have been selected.
If we use an array to represent the mixed-radix number in little-endian digit order, then we would have the following:
int mixed_radix[n] = {0};
You increment this mixed-radix number in the following way:
//Increment the least-significant digit
mixed_radix[0]++;
//Ripple overflow toward the most-significant digit
for(i=0; i<n; i++) {
if(mixed_radix[i] > i) {
mixed_radix[i] = 0;
if(i < n-1)mixed_radix[i+1]++;
}
else {
break;
}
}
So we have a way to initialize the mixed-radix number to zero, and then increment it through every possible value, stopping once it wraps back around to zero. (or after n!-1
increments...)
All that said, there's a lovely numerical trick that can make this even simpler: You can unpack that ugly mixed-radix number from an integer in the following way:
We know that there are n!
permutations of n
items; for any integer val
in the range [0, n!-1]
the following provides a bijective mapping between the integer value and a mixed-radix number:
int working = val; //val in the range [0, n!-1]
for(j=0 j<n; j++) {
mixed_radix[j] = working % (j+1);
working /= (j+1);
}
So embedding this "unpacking" loop within an outer loop that runs val
over the range 0
to n!-1
is a much denser method to enumerate the mixed-radix numbers (which enumerates the possible permutations). It also assigns an integer to each permutation, effectively naming them. :)