7

I'm looking for an algorithm which computes all permutations of a bitstring of given length (n) and amount of bits set (k). For example while n=4 and k=2 the algorithm shall output:

1100
1010
1001
0011
0101
0110

I'm aware of Gosper's Hack which generates the needed permutations in lexicographic order. But i need them to be generated in such a manner, that two consecutive permutations differ in only two (or at least a constant number of) bitpositions (like in the above example). Another bithack to do that would be awesome, but also a algorithmic description would help me alot.

Memphisd
  • 187
  • 7
  • 2
    See Knuth's TAOCP Vol. 4A. It conatins what you're looking for. If you don't have the book available I can write an answer, but I'm no Knuth :). – blazs Apr 06 '16 at 12:25
  • 1
    These are not permutations, they are [Combinations](https://en.wikipedia.org/wiki/Combination), or `n choose k`. – beaker Apr 06 '16 at 14:26
  • 1
    See http://stackoverflow.com/a/127856/1566221 – rici Apr 06 '16 at 16:05
  • @beaker totally right, sorry for that. – Memphisd Apr 06 '16 at 18:28
  • @Memphisd No need to apologize at all. My comment was merely intended to point you to more information. – beaker Apr 06 '16 at 18:30
  • @Memphisd see Section 7.2, _Generating all possibilities_, starting on page 281. – blazs Apr 06 '16 at 18:35
  • @beaker `1100`, `1010`, `1001`, `0011`, `0101` and `0110` are unique permutations, and there are `4! / 2*2 = 6`; the location of the set bits can also be expressed as `(0,1)`, `(0,2)`, `(0,3)`, `(2,3)`, `(1,3)` and `(1,2)`, which are combinations, and there are `(4 choose 2)` or `4*3 / 2 = 6`. So they're basically two ways of looking at the same thing. – m69 ''snarky and unwelcoming'' Apr 07 '16 at 20:37
  • @m69 Yes, so long as you stipulate *unique* permutations. In fact, if you look at it this way, combinations are no more than a special case of unique permutations with 2 "classes", 0 and 1. You can generalize this to *m* classes where the classes are the unique elements of the multiset and *c(i)* is the number of elements in class *i*. In the general case then, the number of unique permutations would be `n! / c(1)!*c(2)!*...*c(m)!` where `c(1)+c(2)+...+c(m) = n`. – beaker Apr 07 '16 at 21:39
  • @blazs Does TAoCP mention the walking-bit algorithm? Or do you know what it's called? (I can't image I stumbled upon an original idea :-) ) – m69 ''snarky and unwelcoming'' Apr 08 '16 at 21:46
  • @m69 I'll check it out tomorrow (I'm going to bed soon). It's unlikely, these are very, very well-studied problems, but you never know. But I'll check. (I once thought I discovered a [new algorithm for generating k-subsets of an n-set](https://github.com/blazs/subsets) only to learn from my TA it was invented in the 1960s.) – blazs Apr 08 '16 at 21:59

2 Answers2

8

Walking bit algorithm

To generate permutations of a binary sequence by swapping exactly one set bit with an unset bit in each step (i.e. the Hamming distance between consecutive permutations equals two), you can use this "walking bit" algorithm; the way it works is similar to creating the (reverse) lexicographical order, but the set bits walk right and left alternately, and as a result some parts of the sequence are mirrored. This is probably better explained with an example:

walking-bit permutations example

Recursive implementation

A recursive algorithm would receive a sequence of n bits, with k bits set, either all on the left or all on the right. It would then keep a 1 at the end, recurse with the rest of the sequence, move the set bit and keep 01 at the end, recurse with the rest of the bits, move the set bit and keep 001 at the end, etc... until the last recursion with only set bits. As you can see, this creates alternating left-to-right and right-to-left recursions.
When the algorithm is called with a sequence with only one bit set, this is the deepest recursion level, and the set bit walks from one end to the other.

walking-bit permutations recursion


Code example 1

Here's a simple recursive JavaScript implementation:

function walkingBits(n, k) {
    var seq = [];
    for (var i = 0; i < n; i++) seq[i] = 0;
    walk (n, k, 1, 0);

    function walk(n, k, dir, pos) {
        for (var i = 1; i <= n - k + 1; i++, pos += dir) {
            seq[pos] = 1;
            if (k > 1) walk(n - i, k - 1, i%2 ? dir : -dir, pos + dir * (i%2 ? 1 : n - i))
            else document.write(seq + "<BR>");
            seq[pos] = 0;
        }
    }
}

walkingBits(7,3);

Translated into C++ that could be something like this:

#include <iostream>
#include <string>

void walkingBits(int n, int k, int dir = 1, int pos = 0, bool top = true) {
    static std::string seq;
    if (top) seq.resize(n, '0');
    for (int i = 1; i <= n - k + 1; i++, pos += dir) {
        seq[pos] = '1';
        if (k > 1) walkingBits(n - i, k - 1, i % 2 ? dir : -dir, pos + dir * (i % 2 ? 1 : n - i), false);
        else std::cout << seq << '\n';
        seq[pos] = '0';
    }
    if (top) seq.clear();
}

int main() {
    walkingBits(7, 3);
}

(See also [this C++11 version][3], written by VolkerK in response to a question about the above code.)

(Rextester seems to have been hacked, so I've pasted Volker's code below.)

#include <iostream>
#include <vector>
#include <functional>

void walkingBits(size_t n, size_t k) {
    std::vector<bool> seq(n, false);
    std::function<void(const size_t, const size_t, const int, size_t)> walk = [&](const size_t n, const size_t k, const int dir, size_t pos){
        for (size_t i = 1; i <= n - k + 1; i++, pos += dir) {
            seq[pos] = true;
            if (k > 1) {
                walk(n - i, k - 1, i % 2 ? dir : -dir, pos + dir * (i % 2 ? 1 : n - i));
            }
            else {
                for (bool v : seq) {
                    std::cout << v;
                }
                std::cout << std::endl;;
            }
            seq[pos] = false;
        }
    };
    walk(n, k, 1, 0);
}

int main() {
    walkingBits(7, 3);

    return 0;
}

Code example 2

Or, if you prefer code where elements of an array are actually being swapped:

function walkingBits(n, k) {
    var seq = [];
    for (var i = 0; i < n; i++) seq[i] = i < k ? 1 : 0;
    document.write(seq + "<BR>");
    walkRight(n, k, 0);

    function walkRight(n, k, pos) {
        if (k == 1) for (var p = pos + 1; p < pos + n; p++) swap(p - 1, p)
        else for (var i = 1; i <= n - k; i++) {
            [walkLeft, walkRight][i % 2](n - i, k - 1, pos + i);
            swap(pos + i - 1, pos + i + (i % 2 ? 0 : k - 1));
        }
    }
    function walkLeft(n, k, pos) {
        if (k == 1) for (var p = pos + n - 1; p > pos; p--) swap(p - 1, p)
        else for (var i = 1; i <= n - k; i++) {
            [walkRight, walkLeft][i % 2](n - i, k - 1, pos);
            swap(pos + n - i - (i % 2 ? 1 : k), pos + n - i);
        }
    }
    function swap(a, b) {
        var c = seq[a]; seq[a] = seq[b]; seq[b] = c;
        document.write(seq + "<BR>");
    }
}

walkingBits(7,3);

Code example 3

Here the recursion is rolled out into an iterative implementation, with each of the set bits (i.e. each of the recursion levels) represented by an object {o,d,n,p} which holds the offset from the leftmost position, the direction the set bit is moving in, the number of bits (i.e. the length of this part of the sequence), and the current position of the set bit within this part.

function walkingBits(n, k) {
    var b = 0, seq = [], bit = [{o: 0, d: 1, n: n, p: 0}];
    for (var i = 0; i < n; i++) seq.push(0);

    while (bit[0].p <= n - k) {
        seq[bit[b].o + bit[b].p * bit[b].d] = 1;
        while (++b < k) {
            bit[b] = {
                o: bit[b-1].o + bit[b-1].d * (bit[b-1].p %2 ? bit[b-1].n-1 : bit[b-1].p+1),
                d: bit[b-1].d * (bit[b-1].p %2 ? -1 : 1),
                n: bit[b-1].n - bit[b-1].p - 1,
                p: 0
            }
            seq[bit[b].o + bit[b].p * bit[b].d] = 1;
        }
        document.write(seq + "<BR>");
        b = k - 1;
        do seq[bit[b].o + bit[b].p * bit[b].d] = 0;
        while (++bit[b].p > bit[b].n + b - k && b--);
    }
}

walkingBits(7, 3); // n >= k > 0

Transforming lexicographical order into walking bit

Because the walking bit algorithm is a variation of the algorithm to generate the permutations in (reverse) lexicographical order, each permutation in the lexicographical order can be transformed into its corresponding permutation in the walking bit order, by mirroring the appropriate parts of the binary sequence.
So you can use any algorithm (e.g. Gosper's Hack) to create the permutations in lexicographical or reverse lexicographical order, and then transform each one to get the walking bit order.

lexicographical to walking bit transform

Practically, this means iterating over the binary sequence from left to right, and if you find a set bit after an odd number of zeros, reversing the rest of the sequence and iterating over it from right to left, and so on...

Code example 4

In the code below the permutations for n,k = 7,3 are generated in reverse lexicographical order, and then transformed one-by-one:

function lexi2walk(lex) {
    var seq = [], ofs = 0, pos = 0, dir = 1; 
    for (var i = 0; i < lex.length; ++i) {
        if (seq[ofs + pos * dir] = lex[i]) {
            if (pos % 2) ofs -= (dir *= -1) * (pos + lex.length - 1 - i)
            else ofs += dir * (pos + 1);
            pos = 0;
        } else ++pos;
    }
    return seq;
}

function revLexi(seq) {
    var max = true, pos = seq.length, set = 1;
    while (pos-- && (max || !seq[pos])) if (seq[pos]) ++set; else max = false;
    if (pos < 0) return false;
    seq[pos] = 0;
    while (++pos < seq.length) seq[pos] = set-- > 0 ? 1 : 0;
    return true;
}
    
var s = [1,1,1,0,0,0,0];
document.write(s + " &rarr; " + lexi2walk(s) + "<br>");
while (revLexi(s)) document.write(s + " &rarr; " + lexi2walk(s) + "<br>");

Homogeneous Gray path

The permutation order created by this algorithm is similar, but not identical, to the one created by the "homogeneous Gray path for combinations" algorithm described by D. Knuth in The Art of Computer Programming vol. 4a, sect. 7.2.1.3, formula (31) & fig. 26c.

walking bit vs. homogeneous (31)

1

This is easy to achieve with recursion:

public static void nextPerm(List<Integer> list, int num, int index, int n, int k) {
    if(k == 0) {
        list.add(num);
        return;
    }

    if(index == n) return;

    int mask = 1<<index;

    nextPerm(list, num^mask, index+1, n, k-1);
    nextPerm(list, num, index+1, n, k);
}

Running this with the client:

public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<Integer>();
    nextPerm(list, 0, 0, 4, 2);
}  

Output:

0011
0101
1001
0110
1010
1100

The idea is to start with the initial number, and consider changing a bit, one index at a time, and to keep track of how many times you changed the bits. Once you changed the bits k times (when k == 0), store the number and terminate the branch.

Maljam
  • 6,048
  • 3
  • 14
  • 30