數學中存在這樣一個序列,它充滿魔力,在實際工程中也有一部分的應用。今天就打算分享一下這個序列,它在 Google S2 中是如何使用的以及它在圖論中,其他領域中的應用。這個序列就是德布魯因序列 De Bruijn。
一. 從一個魔術開始說起
有這樣一個撲克牌魔術。魔術師手上拿著一疊牌,給5個人(這里的人數只能少于等于32,原因稍后會解釋)分別檢查撲克牌,查看撲克牌的花色和點數是否都是不同的,即沒有相同的牌。
檢查完撲克牌,沒有重復的牌以后,就可以給這5個人洗牌了。讓這5個人任意的抽一疊牌從上面放到下面,即切牌。5個人輪流切完牌,牌的順序已經全部變化了。
接著開始抽牌。魔術師讓最后一個切牌的人抽走這疊牌最上面的一張,依次給每個人抽走最上面的一張。這時候抽走了5張牌。魔術師會說,“我已經看透了你們的心思,你們手上的牌我都知道了”。然后魔術師會讓拿黑色牌的人站起來(這一步很關鍵!)。然后魔術師會依次說出所有人手上的牌。最后每個人翻出自己的牌,全部命中。全場歡呼。
二. 魔術原理揭秘
在整個魔術中,有三個地方比較關鍵。第一個是參與的人數只能少于等于32 。一副完整的撲克牌中,總共有54張牌,但是除去2張鬼牌(因為他們花色只有2種),總共就52張牌。
在上述魔術中,所有的牌都用二進制進行編碼,要想任意說出任意連續的5張牌,那么必須這副牌具有全排列的特性。即枚舉所有種組合,并且每個組合都唯一代表了一組排列。
如果窗口大小為5,5張連續的撲克牌。二進制編碼 25 = 32 ,所以需要32張牌。如果窗口大小為6,6張連續的撲克牌,二進制編碼 26 = 64,需要64張撲克牌。總共牌只有52張,所以不可能到64張。所以32個人是上限了 。
第二個關鍵的地方是,只有讓拿黑色的牌的人或者拿紅色的牌的人站出來,魔術師才能知道這5個人拿到的連續5張撲克牌究竟是什么。其實魔術師說“我已經知道你們所有人拿到的是什么牌”的時候,他并不知道每個人拿到的是什么牌。
撲克牌除了點數以外,還有4種花色。現在需要32張牌,就是1-8號牌,每號牌都有4種花色。花色用2位二進制編碼,1-8用3位二進制編碼。于是5位二進制正好可以表示一張撲克牌所有信息。
如上圖,00110 表示的就是梅花6 。11000 表示的是紅桃8(因為沒有 0 號牌,所以000就表示8)
第一步將撲克牌編碼完成以后,第二步就需要找到一個序列,它必須滿足以下的條件:由 2n-1個1和2n-1個0構成的序列或者圓排列,是否能存在在任意 n 個位置上0,1序列兩兩都不同。滿足這個條件的序列也稱為 n 階完備二進圓排列。
這個魔術中我們需要找的是 5 階完備二進圓排列。答案是存在這樣一個滿足條件的序列。這個序列也就是文章的主角,德布魯因序列。
上述序列就是一個窗口大小為5的德布魯因序列。任意連續的5個二進制相互之間都是兩兩不同的。所以給觀眾任意洗牌,不管怎么洗牌,只要最終挑出來是連續的5張,這5張的組合都在最終的結果之中。
將窗口大小為5的德布魯因序列每5個二進制位都轉換成撲克牌的編碼,如下:
所以32張牌的初始順序如下:
梅花8,梅花A,梅花2,梅花4,黑桃A,方片2,梅花5,黑桃3,方片6,黑桃4,紅桃A,方片3,梅花7,黑桃7,紅桃7,紅桃6,紅桃4,紅桃8,方片A,梅花3,梅花6,黑桃5,紅桃3,方片7,黑桃6,紅桃5,紅桃2,方片5,黑桃2,方片4,黑桃8,方片8。
上面這32張牌的初始順序魔術師是要記在心里的。
將所有的排列組合列舉出來,如上圖。當魔術師讓黑色或者紅色的牌的人出列的時候,就能確定到具體是哪一種組合了。于是也就可以直接說出每個人手上拿的是什么牌了。
這個魔術中選取的德布魯因序列也非常特殊,是可以通過一部分的遞推得到。
這個特殊的序列,任意取出其中一個窗口,即5個連續的二進制,5個二進制的第一位和第三位,或者倒數第三位和倒數第五位相加,加法遵循二進制規則,即可得到這個窗口緊接著的下一位。
如上圖的例子,假設當前窗口里面的五位是 00001,左數第一位加上第三位,或者右數第三位加上第五位,得到的是0,那么這個窗口緊接著的后一位就是0 ,即 000010 。再舉一個例子,當前窗口里面是 11000 ,左數第一位加上第三位為1,所以緊接著的下一位是1,即 110001 。
最后一個關鍵的地方就在切牌的手法上。由于德布魯因序列是一個循環的序列,為了維護這個序列,切牌只能把上面的牌切到下面去,不能亂切。只有上面的牌切到下面,因為循環的原因,這樣才不能影響德布魯因序列。
魔術能成功實施,必要條件就是要先記住32張牌的初始位置。然后切牌的時候暗示觀眾牌是洗過的。最后根據觀眾拿了黑色花色的牌(梅花和黑桃)舉手,快速定位到5個連續的牌位于32張牌的哪個窗口,然后說出這5張牌的花色和點數。
三. 德布魯因序列的定義和性質
1. 定義
德布魯因序列(De Bruijn sequence),記為B(k, n),是 k 元素構成的循環序列。所有長度為 n 的 k 元素構成序列都在它的子序列(以環狀形式)中,出現并且僅出現一次。
例如,序列 00010111 屬于B(2,3)。 00010111 的所有長度為3的子序列為000,001,010,101,011,111,110,100,正好構成了 {0,1} 3 的所有組合。
2. 長度
德布魯因序列的長度為 kn。
注意到,所有長度為 n 的 k 元素構成的序列總共有 kn。而對于德布魯因序列中的每個元素,恰好構成一個以此元素開頭長度為 n 的子序列。所以德布魯因序列的長度為 kn 。
3. 數量
德布魯因序列的數量 B(k,n) 的數量為 (k!) ^ (kn-1) / kn 。
我們用數學歸納法證明一下上述的結論。
我們先假設德布魯因序列是二進制的,即 k = 2。想計算序列數量總共有多少個,其實可以看這個序列每個子序列轉換成10進制的數最大的是多少,那么就是它的數量。
由于每相鄰的子序列是相互依賴的關系,比如下一個子序列是前一個子序列左移一位再加上 0 或者 1,產生下一個子序列。當然最后要 mod 2n,這樣控制每個子序列的長度都在 n 位之間。于是我們可以得到這樣的一個式子:
s[i+1]=(2s[i]+(0|1))mod(2^n)
利用錯位相減法,我們可以得到通項公式:
|B(2,n)|= 2 ^ 2(n?1) / 2n
最后利用數學歸納法我們可以得到一個通用的式子,即:
|B(k,n)| 的數量為 (k!) ^ (kn-1) / kn
最最常用的德布魯因序列就是 k = 2 。計算一下 |B(2,n)| 的數量,如下:
4. 生成方式
由于德布魯因序列并不唯一,所以用代碼可以生成其中的任意一種。
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)
二進制的德布魯因序列用的比較多,接下來看看它生成的序列。
B(2,1) 就唯一一種情況。
i 01 s[i]
0 0 0
1 1 1
B(2,2) 由4位二進制位組成。也是唯一一種情況。
i 0011|0 s[i]
0 00 . . . 0
1 01 1
2 . 11 . . 3
3 1|0 2
B(2,3) 由8位二進制位組成。有2種德布魯因序列。
i 00010111|00 s[i] i 00011101|00 s[i]
0 000 . . . . 0 0 000 . . . . 0
1 001 1 1 001 1
2 . 010 . . . 2 2 . 011 . . . 3
3 101 5 3 111 7
4 . . 011 . . 3 4 . . 110 . . 6
5 111 7 5 101 5
6 . . . 11|0 6 6 . . . 01|0 2
7 1|00 4 7 1|00 4
B(2,4) 由16位二進制位組成。對應有16種德布魯因序列。
0x09af 0000100110101111
0x09eb 0000100111101011
0x0a6f 0000101001101111
0x0a7b 0000101001111011
0x0b3d 0000101100111101
0x0b4f 0000101101001111
0x0bcd 0000101111001101
0x0bd3 0000101111010011
0x0cbd 0000110010111101
0x0d2f 0000110100101111
0x0d79 0000110101111001
0x0de5 0000110111100101
0x0f2d 0000111100101101
0x0f4b 0000111101001011
0x0f59 0000111101011001
0x0f65 0000111101100101
取出其中的 0x0d2f :
i 0000110100101111|000 s[i]
0 0000 . . . . . . . . 0
1 0001 1
2 . 0011 . . . . . . . 3
3 0110 6
4 . . 1101 . . . . . . 13
5 1010 10
6 . . . 0100 . . . . . 4
7 1001 9
8 . . . . 0010 . . . . 2
9 0101 5
10 . . . . . 1011 . . . 11
11 0111 7
12 . . . . . . 1111 . . 15
13 111|0 14
14 . . . . . . . 11|00 12
15 1|000 8
B(2,5) 由32位二進制位組成。對應有2048種德布魯因序列。由于太多了,這里沒法一一列舉出來,任意挑選一個出來舉例:
i 00000111011010111110011000101001|0000 s[i]
0 00000 . . . . . . . . . . . . . . . . 0
1 00001 1
2 . 00011 . . . . . . . . . . . . . . . 3
3 00111 7
4 . . 01110 . . . . . . . . . . . . . . 14
5 11101 29
6 . . . 11011 . . . . . . . . . . . . . 27
7 10110 22
8 . . . . 01101 . . . . . . . . . . . . 13
9 11010 26
10 . . . . . 10101 . . . . . . . . . . . 21
11 01011 11
12 . . . . . . 10111 . . . . . . . . . . 23
13 01111 15
14 . . . . . . . 11111 . . . . . . . . . 31
15 11110 30
16 . . . . . . . . 11100 . . . . . . . . 28
17 11001 25
18 . . . . . . . . . 10011 . . . . . . . 19
19 00110 6
20 . . . . . . . . . . 01100 . . . . . . 12
21 11000 24
22 . . . . . . . . . . . 10001 . . . . . 17
23 00010 2
24 . . . . . . . . . . . . 00101 . . . . 5
25 01010 10
26 . . . . . . . . . . . . . 10100 . . . 20
27 01001 9
28 . . . . . . . . . . . . . . 1001|0. . 18
29 001|00 4
30 . . . . . . . . . . . . . . . 01|000 8
31 1|0000 16
B(2,6) 由64位二進制位組成。對應有67,108,864種德布魯因序列。由于太多了,這里沒法一一列舉出來,任意挑選一個出來舉例:
i 0000001000101111110111010110001111001100100101010011100001101101|00000 s[i]
0 000000 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 0
1 000001 1
2 . 000010 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
3 000100 4
4 . . 001000 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
5 010001 17
6 . . . 100010 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
7 000101 5
8 . . . . 001011 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
9 010111 23
10 . . . . . 101111 . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
11 011111 31
12 . . . . . . 111111 . . . . . . . . . . . . . . . . . . . . . . . . . . 63
13 111110 62
14 . . . . . . . 111101 . . . . . . . . . . . . . . . . . . . . . . . . . 61
15 111011 59
16 . . . . . . . . 110111 . . . . . . . . . . . . . . . . . . . . . . . . 55
17 101110 46
18 . . . . . . . . . 011101 . . . . . . . . . . . . . . . . . . . . . . . 29
19 111010 58
20 . . . . . . . . . . 110101 . . . . . . . . . . . . . . . . . . . . . . 53
21 101011 43
22 . . . . . . . . . . . 010110 . . . . . . . . . . . . . . . . . . . . . 22
23 101100 44
24 . . . . . . . . . . . . 011000 . . . . . . . . . . . . . . . . . . . . 24
25 110001 49
26 . . . . . . . . . . . . . 100011 . . . . . . . . . . . . . . . . . . . 35
27 000111 7
28 . . . . . . . . . . . . . . 001111 . . . . . . . . . . . . . . . . . . 15
29 011110 30
30 . . . . . . . . . . . . . . . 111100 . . . . . . . . . . . . . . . . . 60
31 111001 57
32 . . . . . . . . . . . . . . . . 110011 . . . . . . . . . . . . . . . . 51
33 100110 38
34 . . . . . . . . . . . . . . . . . 001100 . . . . . . . . . . . . . . . 12
35 011001 25
36 . . . . . . . . . . . . . . . . . . 110010 . . . . . . . . . . . . . . 50
37 100100 36
38 . . . . . . . . . . . . . . . . . . . 001001 . . . . . . . . . . . . . 9
39 010010 18
40 . . . . . . . . . . . . . . . . . . . . 100101 . . . . . . . . . . . . 37
41 001010 10
42 . . . . . . . . . . . . . . . . . . . . . 010101 . . . . . . . . . . . 21
43 101010 42
44 . . . . . . . . . . . . . . . . . . . . . . 010100 . . . . . . . . . . 20
45 101001 41
46 . . . . . . . . . . . . . . . . . . . . . . . 010011 . . . . . . . . . 19
47 100111 39
48 . . . . . . . . . . . . . . . . . . . . . . . . 001110 . . . . . . . . 14
49 011100 28
50 . . . . . . . . . . . . . . . . . . . . . . . . . 111000 . . . . . . . 56
51 110000 48
52 . . . . . . . . . . . . . . . . . . . . . . . . . . 100001 . . . . . . 33
53 000011 3
54 . . . . . . . . . . . . . . . . . . . . . . . . . . . 000110 . . . . . 6
55 001101 13
56 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 011011 . . . . 27
57 110110 54
58 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101101 . . . 45
59 01101|0 26
60 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1101|00 . 52
61 101|000 40
62 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 01|0000 16
63 1|00000 32
B(2,5) 和 B(2,6) 在實際生產中都有廣泛的用途。
四. 在圖論中的應用:歐拉回路 和 漢密爾頓回路
在圖論中,有這樣一種無向連通圖,有一條通路,能經過這個圖的每條邊一次并且僅一次的路徑被稱為歐拉回路。這個問題也是最常見的哥尼斯堡七橋問題:能不能一次走遍所有的7座橋,并且每座橋只經過一次。其實就是判斷是否存在歐拉回路。
與歐拉問題非常類似的是漢密爾頓回路的問題。該問題起源于英國數學家威廉漢密爾頓(Willian Hamilton)于1857年發明的一個關于正十二面體的數學游戲:正十二面體的每個棱角上標有一個當時非常有名的城市,游戲的目的是“環繞地球”旅行,也就是說,尋找一個環游路線使得經過每個城市一次且恰好一次。
如果把正十二面體的20個棱角看成圖中的頂點,將正十二面體畫成如上圖的平面圖,那么問題就轉換成:能否在圖中找到一條回路,經過每個頂點一次有且僅有一次。上圖就給出了一條符合要求的回路。
歐拉回路的問題一般求解方法有兩種,DFS 和 Fleury 佛羅萊算法。但是漢密爾頓圖沒有一個有效的判斷方法,它只是給出了一些充分條件或者必要條件,并非充要條件。
德布魯因序列 和 歐拉回路,漢密爾頓回路 有緊密的聯系。
若由 k 種符號組成的所有長度為 n 的序列列表為有向圖的頂點,則圖中有 kn 個頂點, 若頂點 m 去掉第一個符號并在尾端添加一個符號便可得頂點 n ,則有一個有向邊由 m 指向 n,此時圖就是 德布魯因圖。如下圖,k = 2, n = 3 的圖中,頂點 010 有兩條對外的邊,分別指向 101 和 100 。
我們以 B(2,3) 舉例。
在德布魯因圖中的漢密爾頓回路 即為 德布魯因序列。如下圖,左圖中紅色的漢密爾頓回路 ,圖中對應的德布魯因序列是 00010111,且這個漢密爾頓回路等價于窗口長度為 2 的德布魯因序列中的一個歐拉回路,見下右圖中標記的歐拉回路對應的順序編號。
所以,窗口為 n 的德布魯因圖中的漢密爾頓回路可以等價轉換為窗口為 n - 1 的德布魯因圖中的歐拉回路。
當然,一個德布魯因圖中的漢密爾頓回路并不一定唯一。如上左圖,同樣是 k = 2,n = 3 的德布魯因圖,還可以找到一條漢密爾頓回路。對應的歐拉回路的順序也對應的發生了變化,見右圖。
這也說明了,k = 2,n = 3 的時候,德布魯因序列存在多個,并不唯一。
再進一步,既然說高階的德布魯因圖的漢密爾頓回路可以轉換成低級的歐拉回路。那么我們就用 k = 2,n = 3 的德布魯因圖的歐拉回路去構造高階的漢密爾頓圖,可以么?答案是當然可以。
如上圖,用 k = 2,n = 3 的德布魯因圖中的一條歐拉回路,我們構造出了 k = 2,n = 4 的德布魯因序列。
同理,當 k = 3,n = 2 的時候,德布魯因圖中依舊可以找到一條漢密爾頓回路,與之對應的 n = 1 的窗口的歐拉回路也存在。如下圖。
五. 位掃描器
德布魯因序列用的比較廣泛的一點應用就是 位掃描器。在 Google S2 中也是這個作用。
先來看一個比較常見的問題。
有一個非0的正數,它用二進制表示的。問如何快速的找到它二進制位中最后的1所在的位置。例如,0101010010010100,它的最后一個1所在的位置是從右往左數的第2位(從0開始數)。
這道題有幾種做法,從粗糙到最優依次分析分析。
最直接的想法是能把這個二進制數轉換成只有一位為1的情況。如果上面這個問題轉換成只有一個位上為1的情況,那很好解決。
那么問題轉化為如何把末尾的1分離出來。可以直接用位運算進行分離。
x &= (~x+1)
// 或者
x &= -x
通過上面的操作可以把最后一位的1分離出來。
分離出來以后的解法就很多種了。
舉個例子:
假設某個64位的二進制數如下:
3932700031202623488
11011010010011110000011101011110010000000000000000000000000000
經過 x & -x 操作以后,就能把末尾的1篩出來。原理是因為負數的存儲方式是以原碼的補碼,即符號位不變,每位取反再加1 。末尾連續的0取反以后都為1,所以加1以后會一直往前進位,直到原來為1的那一位,因為它取反以后這一位變成0了,就不會往前進位了。
1. 循環
可以用 for 循環,不斷的右移目標數。找到末尾的1所在的位置。
for ( index = -1; x > 0; x >>= 1, ++index ) ;
這種方式簡單粗暴,時間復雜度為 O(n) 。
2. 二分搜索
把上述循環改成二分,時間復雜度就變成了 O(lgn)
3. 構造特殊數字進行位運算
這種方式看上去比較巧,但是實際還是利用了二分搜索的思想。
index = 0;
index += (!!(x & 0xAAAAAAAA)) * 1;
index += (!!(x & 0xCCCCCCCC)) * 2;
index += (!!(x & 0xF0F0F0F0)) * 4;
index += (!!(x & 0xFF00FF00)) * 8;
index += (!!(x & 0xFFFF0000)) * 16;
這種方式的時間復雜度也是 O(lgn) ,但是實際計算會比二分搜索快很多,因為它不需要比較運算,都位運算即可完成。
5. 哈希
這種方式就比之前的方式都要高效了。
假設 x 有32位,所以末尾的1出現的可能只有32種。如果 x 為64,那就是64種可能,每一位上都有可能。通過哈希的方式 O(1) 的時間復雜度查詢出結果。
6. 德布魯因序列
這種方式的原理也是哈希,但是這種方式比單純的哈希要快,因為它避免的取余的計算。
如果 x 為32位,那么哈希函數可以構造成下面這樣:
(x * 0x077CB531) >> 27
0x077CB531 是32位的德布魯因序列之一。
構造這樣一個哈希函數有2點優點:
- 由于這個二進制數是我們篩選出來的特殊的二進制數,原來的二進制數的末尾的1被我們篩選出來了。它本身其實就是二的次方,所以任何一個數字乘以這個特殊的二進制的數,都相當于左移運算。左移的位數就是原二進制數末尾1所在的位子。
- 德布魯因序列相當于是全排列,枚舉了所有的情況。所以它的兩兩子序列之間肯定不相同,這就是完美的哈希。
最后又移動27位是為了保證能取出開頭的5位。
在 Go 的原生代碼包中,有一個 nat.go 文件,在這個文件里面有這樣一段代碼:
const deBruijn32 = 0x077CB531
var deBruijn32Lookup = []byte{
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9,
}
const deBruijn64 = 0x03f79d71b4ca8b09
var deBruijn64Lookup = []byte{
0, 1, 56, 2, 57, 49, 28, 3, 61, 58, 42, 50, 38, 29, 17, 4,
62, 47, 59, 36, 45, 43, 51, 22, 53, 39, 33, 30, 24, 18, 12, 5,
63, 55, 48, 27, 60, 41, 37, 16, 46, 35, 44, 21, 52, 32, 23, 11,
54, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6,
}
在這個文件中,同樣有一個函數在解決上述的問題,只不過它換了一個角度。
求一個二進制數的末尾1所在的位置,其實可以轉化為求這個二進制數末尾連續0有多少個的問題。
這個經典的問題在圖靈獎獲得者 Donald Ervin Knuth 的著作 《計算機程序設計藝術》的第四卷,section 7.3.1 上有,感興趣的同學可以看看這個問題。
// trailingZeroBits returns the number of consecutive zero bits on the right
// side of the given Word.
// See Knuth, volume 4, section 7.3.1
func trailingZeroBits(x Word) int {
// x & -x leaves only the right-most bit set in the word. Let k be the
// index of that bit. Since only a single bit is set, the value is two
// to the power of k. Multiplying by a power of two is equivalent to
// left shifting, in this case by k bits. The de Bruijn constant is
// such that all six bit, consecutive substrings are distinct.
// Therefore, if we have a left shifted version of this constant we can
// find by how many bits it was shifted by looking at which six bit
// substring ended up at the top of the word.
switch _W {
case 32:
return int(deBruijn32Lookup[((x&-x)*deBruijn32)>>27])
case 64:
return int(deBruijn64Lookup[((x&-x)*(deBruijn64&_M))>>58])
default:
panic("Unknown word size")
}
return 0
}
這里還需要解釋一下 deBruijn32Lookup 和 deBruijn64Lookup 數組里面初始裝入的數字到底代表了什么意思。
deBruijn32 和 deBruijn64 分別是德布魯因序列。兩兩之間的子序列都是不相同的,并且所有的子序列構成了一個全排列。
const deBruijn32 = 0x077CB531
// 0000 0111 0111 1100 1011 0101 0011 0001
const deBruijn64 = 0x03f79d71b4ca8b09
// 0000 0011 1111 0111 1001 1101 0111 0001 1011 0100 1100 1010 1000 1011 0000 1001
我們用下面的哈希函數構造一個“完美”的哈希函數。
h(x) = (x * deBruijn) >> (n - lg n)
n 是二進制數的位數。于是也就可以理解 ((x&-x)deBruijn32)>>27 和 ((x&-x)(deBruijn64&_M))>>58 是什么意思了,這就是在算對應的哈希值。
那么數組里面存的值就是我們最終的結果了,即末尾1所在的位置或者末尾連續有多少個0 。
其實數組里面存的數字是這樣算出來的:
void setup( void )
{
int i;
for(i=0; i<32; i++)
index32[ (debruijn32 << i) >> 27 ] = i;
}
即把算出來的哈希值作為下標,對應下標存儲的值是左移的位數。這個左移的位數就是我們要的結果。所以先算哈希值,然后通過數組取出哈希值里面存儲的值即為我們的結果了。
舉例,假設原二進制數是64位的
0011011011101100110011001001001111110011000000000000000000000000
x & -x 以后的結果
1000000000000000000000000
經過這一步以后就把二進制的末尾1截取出來了。剩下的問題就是要找這個1從右往左數在第幾位了。
用64位的德布魯因序列來求:
0000001111110111100111010111000110110100110010101000101100001001
用上面 x & -x 算出來的結果乘以這個64位的德布魯因序列,就相當于左移幾位,直接看0的個數,我們可以看出來是左移24位:
0111000110110100110010101000101100001001000000000000000000000000
最后取出這個數的前6位就是我們要的結果了。lg 64 = 6,所以取出前6位,011100 。
// findLSBSetNonZero64 returns the index (between 0 and 63) of the least
// significant set bit. Passing zero to this function has undefined behavior.
//
// This code comes from trailingZeroBits in https://golang.org/src/math/big/nat.go
// which references (Knuth, volume 4, section 7.3.1).
func findLSBSetNonZero64(bits uint64) int {
return int(deBruijn64Lookup[((bits&-bits)*(deBruijn64&digitMask))>>58])
}
上述程序和之前的實現方式完全一致,只不過這里函數名的意義代表查找末尾1的位置,和查找末尾有多少個0,完全一致!
上述代碼也是 Google S2 中的源碼,它也是直接利用德布魯因序列來查找末尾1所在的位。
總結一下,數組的下標和我們構造的哈希函數值相對應,哈希函數的值其實就是對應德布魯因序列的前幾位。而末尾1所在的位置被我們處理成了一個特殊的二進制數了,這個數的1所在的位置就是原二進制數末尾1所在的位置。這個特殊的二進制數末尾1后面全是0,所以任何數乘以它以后都相當于左移末尾0的個數。于是末尾1所在的位置就被轉換成德布魯因序列左移多少位的問題了。德布魯因序列左移多少位,取出前幾位,生成的數字就是哈希函數的值,它又和數組的下標關聯起來了。于是最后查找數組里面對應哈希值位里面存的值就是我們要的結果了。
六. 工業應用
De Bruijn 序列的奇妙不僅體現在魔術上。我們還可以使用它為機器人做路標定位:將兩種不同顏色的小方塊排成一條長線擺在機器人行進的路上,機器人只要識別出自己前后的幾個方塊是什么顏色,既不需要GPS,也不需要高精度探測儀,就可以知道自己走了多少米。
研究人員利用 De Bruijn 序列設計了每次可以產生一個用于加密的不同隨機數字的簡單電子元件“反饋移位寄存器”,上一個隨機數字和下一個隨機數字之間只改變一個數位和移位一下就可以,電路構造非常簡單。
在測量工程上,德布魯因序列還可以用在基于光柵投影模式的三維形貌快速測量系統研究中。
在基因工程中,德布魯因序列可以用在基因組重復區域組裝上。
在醫學研究中,德布魯因序列通常用于神經科學和心理實驗以檢測刺激序列對神經系統的影響,其可專門用于功能磁共振成像。
在人工智能算法中,神經網絡時間序列預測也有德布魯因序列的身影。
Reference:
Wiki De Bruijn sequence
Wolfram Mathworld de Bruijn Sequence
http://chessprogramming.wikispaces.com/De+Bruijn+sequence
The On-Line Encyclopedia of Integer Sequences
De Bruijn cycle generator
On line Sequence Generator
de Bruijn cycles for neural decoding
De Bruijn sequence
《Using de Bruijn Sequences to Index a 1 in a Computer Word》
空間搜索系列文章:
空間搜索系列文章:
如何理解 n 維空間和 n 維時空
高效的多維空間點索引算法 — Geohash 和 Google S2
Google S2 中的 CellID 是如何生成的 ?
Google S2 中的四叉樹求 LCA 最近公共祖先
神奇的德布魯因序列
四叉樹上如何求希爾伯特曲線的鄰居 ?
GitHub Repo:Halfrost-Field
Follow: halfrost · GitHub