n bits 依次变一位排列的算法

题目要求对长为n的bit数组输出所有0、1组合,相邻两个只有一个bit发生变化。如果n=2,则可以有下面的输出:

00 00
01 01
11 10
10 11

那么左边的是正确的,右边的不正确,因为01和10之间差了两位。

首先想到的办法是如下递归,设f(n)返回一个长度为n的bit数组,并且数组按照题目要求的那样排列。那么f(n+1)可以如下获得:

f(n+1)={1+f(n)的每个元素} 连接 {0 +f(n)的所有元素的倒排}

由于f(n)的元素满足要求,其倒排肯定也满足要求。唯一要证明的是接头的地方是否满足要求。容易看到f(n)的最后一个元素必然等于f(n)倒排的第一个元素,因此连接处也是满足要求的。

算法如下:

def changeOneBit(n):
    if(n==0):
        return [[]];
    else:
        xs = changeOneBit(n-1)
        ys0=[]
        for x in xs:
            ys0.append([0]+x)
            ys0.insert(0,[1]+x)
        return ys0

这个算法有个缺点是需要生成很多临时数组,并且最后需要一个长度为2的n次方的大数组来保存结果。能不能找到一组就立刻输出,不保存呢?回到最初的递归表达式:

f(n)={0+f(n-1)} 并上 {1+f(n-1)}

可以得到如下树结构:

pic

注意到第三层的11和10是倒过来的,按照原始算法下先是10再是11。由此我们想到有两种递归:

f(n)={0+f(n-1)} 并上 {1+f(n-1)}先0后1

f(n)={1+f(n-1)} 并上 {0+f(n-1)}先1后0

需要结合起来,因此有如下的表达式:

f(n,bitFlag)={x+f(n-1, bitFlag’)} 并上 {x’+f(n-1, bitFlag’)}

那么x是0还是1呢,bitFlag’又该怎么变化呢?

显然bitFlag为0的时候表示正常,为1表示需要翻转。正常情况先0后1,翻转情况先1后0.

那什么时候需要翻转?注意到如果父节点包含奇数个1则需要翻转。因为算法保证f(n-1)的序列满足相邻数只差一个bit的需求,那么要想它的下一层是相连接的,必须要求相邻的两个子树是镜像。也就是说如果i子树翻转了,i+1就不能翻转。00显然不翻转,故01翻转,11不翻转,10翻转。

因此算法表达式为:

f(n,bitFlag)={0+f(n-1, bitFlag ^ x)} 并上 {1+f(n-1, bitFlag ^ x)} bitFlag==0

f(n,bitFlag)={1+f(n-1, bitFlagx ^ x)} 并上 {0+f(n-1, bitFlag ^ x)} bitFlag==1

代码如下,if bit0那个可以压缩。但是为了方便理解,就不压缩了:

line=0;
def changeOneBit1(a, n, bit0):
    global line;
    if(n==0):
        line=line+1
        print str(line)+':'+str(a);
    else:
        if(bit0==0):
            a[n-1]=0;
            changeOneBit1(a,n-1,bit0 ^ a[n-1]);
            a[n-1]=1;
            changeOneBit1(a,n-1,bit0 ^ a[n-1]);
        else:
            a[n-1]=1;
            changeOneBit1(a,n-1,bit0 ^ a[n-1]);
            a[n-1]=0;
            changeOneBit1(a,n-1,bit0 ^ a[n-1]);

运行结果如下:

1:[0, 0, 0, 0]
2:[1, 0, 0, 0]
3:[1, 1, 0, 0]
4:[0, 1, 0, 0]
5:[0, 1, 1, 0]
6:[1, 1, 1, 0]
7:[1, 0, 1, 0]
8:[0, 0, 1, 0]
9:[0, 0, 1, 1]
10:[1, 0, 1, 1]
11:[1, 1, 1, 1]
12:[0, 1, 1, 1]
13:[0, 1, 0, 1]
14:[1, 1, 0, 1]
15:[1, 0, 0, 1]
16:[0, 0, 0, 1]

总结一下,这个题目其实只是数据排列的一个小小的变化,但是需要透彻的理解递归的本质,写出递归表达式,并进一步描绘递归空间树,找出节点的规律,才能进一步实现算法。
Technorati Tags: 算法,递归
原文地址:https://www.cnblogs.com/alphablox/p/2999178.html