通过交换两位(不是按字典顺序)排列二进制数的排列

时间:2016-04-06 12:24:24

标签: algorithm permutation bit combinatorics

我正在寻找一种算法,该算法计算给定长度(n)的比特串的所有排列和设置的比特量(k)。例如,当算法输出n=4k=2时:

1100
1010
1001
0011
0101
0110

我知道Gosper的Hack,它以字典顺序生成所需的排列。但我需要以这样的方式生成它们,即两个连续的排列仅在两个(或至少是一个恒定数量的)位置上不同(如上例中所示)。 这样做的另一个好处是很棒,但算法描述也会对我有所帮助。

2 个答案:

答案 0 :(得分:7)

步行位算法

通过在每个步骤中使用未设置位恰好交换一个设置位来生成二进制序列的排列(即连续排列之间的汉明距离等于2),您可以使用这个"步行位"算法;它的工作方式类似于创建(反向)字典顺序,但是设置位交替左右移动,因此序列的某些部分被镜像。用一个例子可能更好地解释了这一点:

walking-bit permutations example

递归实施

递归算法将接收n位序列,其中k位设置为全部在左侧或全部在右侧。然后它会在末尾保持1,与序列的其余部分递归,移动设置位并保持01在末尾,与其余位递归,移动设置位和将001保留在最后,等等......直到最后一次只有设置位的递归。如您所见,这会产生交替的从左到右和从右到左的递归 当使用仅设置一个位的序列调用算法时,这是最深的递归级别,并且设置位从一端走到另一端。

walking-bit permutations recursion

代码示例1

这是一个简单的递归JavaScript实现:



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);
&#13;
&#13;
&#13;

翻译成C ++,可能是这样的:

#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);
}

(参见[此C ++ 11版本] [3],由VolkerK撰写,以回答有关上述代码的问题。)

(Rextester似乎被黑了,所以我在下面粘贴了Volker的代码。)

#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;
}

代码示例2

或者,如果您更喜欢实际交换数组元素的代码:

&#13;
&#13;
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);
&#13;
&#13;
&#13;

代码示例3

这里递归被推广到一个迭代实现中,每个设置位(即每个递归级别)由一个对象{o,d,n,p}表示,它保存从最左边位置的偏移量,即方向集合bit正在移入,位数(即序列的这一部分的长度),以及该部分中设置位的当前位置。

&#13;
&#13;
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
&#13;
&#13;
&#13;

将词典顺序转换为行走位

因为步行比特算法是算法的变体,以(反向)词典顺序生成排列,所以按字典顺序排列的每个排列都可以通过镜像相应的部分来转换为步行比特顺序中的相应排列。二进制序列 因此,您可以使用任何算法(例如Gosper&#s; Hack)以字典或反向词典顺序创建排列,然后转换每个算法以获得步行位顺序。

lexicographical to walking bit transform

实际上,这意味着从左到右迭代二进制序列,如果在奇数个零后找到一个设置位,则反转序列的其余部分并从右到左迭代它,依此类推。 ..

代码示例4

在下面的代码中,n,k = 7,3的排列以反向字典顺序生成,然后逐个转换:

&#13;
&#13;
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>");
&#13;
&#13;
&#13;

同质灰色路径

此算法创建的排列顺序与组合&#34;同构灰色路径创建的排列顺序相似但不完全相同。 D. Knuth在 The Art of Computer Programming vol。中描述的算法。 4a,sect。 7.2.1.3,公式(31)&amp;图。 26C。

walking bit vs. homogeneous (31)

答案 1 :(得分:1)

使用递归很容易实现:

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);
}

使用客户端运行:

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

输出:

  

0011
  0101
  1001
  0110
  1010
  1100

我们的想法是从初始数字开始,并考虑一次更改一个位,一个索引,并跟踪您更改位的次数。更改位k次(k == 0时)后,存储数字并终止分支。