This is a roughly O(n) algorithm that generates the kth lexicographically ordered permutation of an n-element array from the integer k. Example, for a three-element
array:
0 --> 0 1 2
1 --> 0 2 1
2 --> 1 0 2
3 --> 1 2 0
4 --> 2 0 1
5 --> 2 1 0
1. ...Visual Studio 2017Projectspermutepermutepermute.cpp 1
/**
* @mainpage
* @anchor mainpage
* @brief
* @details
* @copyright Russell John Childs, PhD, 2019
* @author Russell John Childs, PhD
* @date 2019-07-14
*
*
* This is an O(n) algorithm that generates the kth lexicographically ordered
* permutation of an n-element array from the integer k.
* Example, for a three-element array:
* 0 --> 0 1 2
* 1 --> 0 2 1
* 2 --> 1 0 2
* 3 --> 1 2 0
* 4 --> 2 0 1
* 5 --> 2 1 0
*
* It utilises the factoradic representation of k. Algorithms generally require
* O(n^2) decoding of the factoradic into a permutation, due to the need to
* delete elements, O(n), from the array n times as they are moved to the
* permutation.
*
* This algorithm decodes the factoradic in O(n), by using a hash
* function, invented by the author, instead of deletions.
*
* This algorithm has a better order of complexity than std::next_permutation,
* which is O(n), but would require O(k*n) to iterate to the kth permutation.
*
* Limitations: O(n) is achieved through a hash function that is limited to
* built-in types up to 128-bit integers. This limits n to 128,
* i.e. the array cannot be larger than 128. This can be extended using
* multiprecision integers or std::bitset, but the order-of-complexity will be,
* at best, O(n * log2 n).
*
* The code is undocumented and questions should be directed to the author.
*
* @file permute.cpp
* @see
* @ref mainpage
*/
#include <vector>
#include <algorithm>
#include <numeric>
#include <limits>
#include <iostream>
#include <iomanip>
#include <bitset>
#include <random>
template<typename T = unsigned long long>
unsigned hash(T h, unsigned ind)
{
2. ...Visual Studio 2017Projectspermutepermutepermute.cpp 2
const unsigned char_bit = std::numeric_limits<unsigned char>::digits;
auto c = [char_bit](T u)
{
u = u - ((u >> 1) & (T)~(T)0 / 3);
u = (u & (T)~(T)0 / 15 * 3) + ((u >> 2) & (T)~(T)0 / 15 * 3);
u = (u + (u >> 4)) & (T)~(T)0 / 255 * 15;
return (T)(u * ((T)~(T)0 / 255)) >> (sizeof(T) - 1) * char_bit;
};
T i = (((T)(1) << (ind + 1)) - 1) & h;
auto j = ind + c(i);
auto prev = j+1;
while (j != 0 && j != prev)
{
prev = j;
i = h >> j;
i = static_cast<T>(std::log2((~i) & (i + 1)));
j += i;
j = ind + c(((h << (sizeof(T)*char_bit - j)) >> (sizeof(T)*char_bit - j)));
}
return static_cast<unsigned>(j);
}
template<typename T = unsigned long long>
std::vector<unsigned>
factoradic(unsigned size, T lehmer)
{
std::vector<unsigned> ret_val(size, 0);
for (unsigned i = 1; lehmer != 0; ++i)
{
ret_val[ret_val.size() - i] = static_cast<unsigned>(lehmer % i);
lehmer /= i;
}
return ret_val;
}
template<typename T = unsigned long long>
std::vector<unsigned> permute(unsigned size, T i)
{
std::vector<unsigned> ret_val(size);
std::vector<unsigned> start(size);
std::iota(start.begin(), start.end(), 0);
auto lehmer = factoradic(size, i);
T h = 0;
for (unsigned j = 0; j < lehmer.size(); ++j)
{
auto ind = hash(h, lehmer[j]);
ret_val[j] = start[ind];
h |= (T)1 << ind;
}
return ret_val;
}
template<typename T = unsigned long long>
void test_permute(unsigned size)
{
T factorial = static_cast<T>(std::tgamma(size + 1));
3. ...Visual Studio 2017Projectspermutepermutepermute.cpp 3
factorial = factorial == 0 || factorial > 120 ? 120 : factorial;
for (T i = 0; i < factorial; ++i)
{
for (auto& elem : permute(size, i)) std::cout << elem << " ";
std::cout << std::endl;
}
}
int main(void)
{
test_permute(5);
}