关于deBruijn序列

关于deBruijn序列、它的生成、以及它的用途。

一、关于deBruijn序列

deBruijn序列是一串字符,满足如下条件:deBruijn序列的长度为n的所有子循环序列不重复。这里的子循环序列,包括字符尾部到头部拼接的序列。

举两个例子:

(1)序列00010111,为长度3的deBruijn序列。

其所有长度为3的子循环序列为:

000
001
010
101
011
111
110(尾部两个字符+头部一个字符)
100(尾部一个字符+头部两个字符)

刚好都不重复,所以此序列为3阶deBruijn序列。

(2)序列aabb,为长度2的deBruijn序列。

其所有长度2的子循环序列为:

aa
ab
bb
ba(尾部一个字符+头部一个字符)

刚好也不重复,所以此序列为2阶deBruijn序列。

 

这里有几个基本概念:

n:子循环序列的长度,上面例子1中为3,例子2中为2
A:字母表,即deBruijn序列包含的字符,上面例子1中即为[0,1],例子2中即为[a,b]
k:字母表包含的字符个数,上面两个例子中均为2
B(k,n):deBruijn序列,其长度为k**n(n次方),上面两个例子中分别为8和4;总共有((k!)^(k**(n-1))/(k**(n))个不同的deBruijn序列

后续会以这几个变量进行计算

 

二、deBruijn序列的生成

如下为一种生成方式: 

import sys
def de_bruijn(k, n):
    """
    de Bruijn sequence for alphabet k
    and subsequences of length n.
    """
    try:
        # let's see if k can be cast to an integer;
        # if so, make our alphabet a list
        _ = int(k)
        alphabet = list(map(str, range(k)))

    except (ValueError, TypeError):
        alphabet = k
        k = len(k)

    a = [0] * k * n
    sequence = []

    def db(t, p):
        if t > n:
            if n % p == 0:
                sequence.extend(a[1:p + 1])
        else:
            a[t] = a[t - p]
            db(t + 1, p)
            for j in range(a[t - p] + 1, k):
                a[t] = j
                db(t + 1, t)
    db(1, 1)
    return "".join(alphabet[i] for i in sequence)

if __name__ == "__main__":
    if len(sys.argv) == 3:
        k=sys.argv[1]
        if k.isdigit():
            k=int(k)
        n=int(sys.argv[2])
        print(k,n)
        print(de_bruijn(k,n))
        exit()
    else:
        print("Usage: %s k n"%sys.argv[0])

 

三、deBruijn序列的用途

(1)典型的用途:快速查找数据最后0的个数,或者最后一个1是第几位(最低位为第0位)。(如果数据全0,则返回位数)

如数字int8 x=80(0101 0000),最后4个0。使用deBruijn序列可以快速计算出来。 分为如下几步: 

首先,通过如下运算,隔离出最后一个1:

y = x & (-x)

由于x=0101 0000,则-x=1011 0000。

y = x & (-x) = 0001 0000 = 16 即为仅存在最后一个1的数字。

其次,建立一个hash索引,使y能够索引到最后的结果4,同时确保hash表最小。

 

在这里,int8有8位,因此最小的hash表项为8个,可以利用长度为8的deBruijn序列的子序列不重复的特性来构造。

deBruijn序列B(k=2,n=3):00010111
子序列-》映射值
000  -》1
001  -》2
010  -》3
101  -》4
011  -》5
111  -》6
110  -》7
100  -》8

最后,从y计算出对应的子序列z:

z = (y*deBruijn) >> (k**n-n)

这里int8 z = (16 * 23) >>(8-3)  = 011

对应的代码如下: 

import math

def setupN(debruijnN,mapN,N):
    n = int(math.log(N,2))
    mask = (1<<N)-1
    for i in range(N):
        mapN[((debruijnN << i) & mask ) >> (N-n)]=i

def getIndex8(x):
    mask = (1<<8) -1 
    y = x&(-x)
    i = ((y*debruijn8) & mask )   >> (8-3)
    z = int((y - 1) >> 4 & 8)
    return map8[i] + z
    
def getIndex32(x):
    mask = (1<<32) -1 
    y = x&(-x)
    i = ((y*debruijn32) & mask )   >> (32-5)
    z = int((y - 1) >> 26 & 32)
    return map32[i] + z
    
def getIndex64(x):
    mask = (1<<64) -1 
    y = x&(-x)
    i = ((y*debruijn64) & mask )   >> (64-6)
    z = int((y - 1) >> 57 & 64)
    return map64[i] + z
  
debruijn8str = de_bruijn(2,3)
debruijn32str = de_bruijn(2,5)
debruijn64str = de_bruijn(2,6)
debruijn8 = int("0b%s"%debruijn8str, 2)
debruijn32 = int("0b%s"%debruijn32str, 2)
debruijn64 = long("0b%s"%debruijn64str, 2)
map8={}
map32={}
map64={}

setupN(debruijn8, map8,8)
setupN(debruijn32, map32,32)
setupN(debruijn64, map64,64)

print(getIndex8(0x10))
print(getIndex8(0x1))
print(getIndex8(0))
print(getIndex32(0x11000000))
print(getIndex32(0x1))
print(getIndex32(0))
print(getIndex64(0x1100000000000000))
print(getIndex64(0x11))
print(getIndex64(0))

在getIndexN函数中,由于python不支持如int8, int32的类型,因此加入了mask来去除溢出的位。 其他语言支持对应类型的则不需要。 

 

附:参考资料

wiki:https://en.wikipedia.org/wiki/De_Bruijn_sequence

posted @ 2019-04-03 16:11  Bright.Hoo  阅读(2868)  评论(1编辑  收藏  举报