1. 程式人生 > >德布魯因序列與indexing 1

德布魯因序列與indexing 1

[TOC] 部落格:[部落格園](https://www.cnblogs.com/shine-lee/) | [CSDN](https://blog.csdn.net/blogshinelee) | [blog](https://blog.shinelee.me/) # 寫在前面 在數值計算中,為了控制精度以及避免越界,需要嚴格控制數值的範圍,有時需要知道二進位制表示中"left-most 1"或"right-most 1”的位置,這篇文章就來介紹一下通過**德布魯因序列(De Bruijn sequence)**來快速定位的方法。 # 標記left-most 1與right-most 1 對於一個二進位制數$v$,如何僅保留最低位或最高位的1? 最低位的1,即right-most 1,其特點是這一位右側均為0,可通過`v & -v`或者`v & ((~v)+1)`來標記最低位的1。 比如`0101 1010`,取反後為`1010 0101`,再加1為`1010 0110`,與後為`0000 0010`。 最高位的1,即left-most 1,其特點是這一位左側均為0,可通過下面來標記最高位的1。 ```cpp uint32_t keepHighestBit( uint32_t n ) { n |= (n >> 1); n |= (n >> 2); n |= (n >> 4); n |= (n >> 8); n |= (n >> 16); return n - (n >> 1); } ``` 前5行移位將最高位1右側的所有位均置為1,`n-(n >> 1)`再將他們清0。 至此,我們已經得到了一個二進位制的“one hot”表示,只有1位為1,它標記了最高位或最低位1的位置。 # 確定位置 假設,得到的“one hot”表示為`0000 0100 0000 0000`,如何確定1在哪一位呢? 比較直接的想法是通過移位計數,不斷右移,並計數,直到最低位為1。 有沒有更好的方法? 令得到的“one hot”表示為`h`,對於`uint32`,`h`只有32種,我們希望找到的這32種one hot表示與$0\sim 31$的對映關係,即$f(h) \rightarrow 0\sim 31$。 - **查表**:以`h`對應的`uint32`數為下標,構建陣列,通過查表方式得到,但`h`最大為$2^{31}$,直接構建陣列不現實 - **雜湊**:再增加一層對映,$f(g(h)) \rightarrow 0\sim 31$,即找到一個hash函式$g$,先將$h$對映到$0 \sim 31$,再通過查表$0\sim 31 \rightarrow 0\sim 31$,但一般雜湊會涉及到取餘操作,還要考慮不要有碰撞 對這個特殊問題,可以使用 德布魯因序列——可視為一種特殊的雜湊,不需要取餘,且絕不會發生碰撞。 # 德布魯因序列(De Bruijn sequence) 先看一個德布魯因序列的例子,令字符集$A = \{0, 1\}$,字元有$k=2$種,子串長度$n=2$,則所有可能的子串有$\{00, 01, 10, 11\}$,則迴圈序列$0011$是一個德布魯因序列,$0011$的所有連續子串恰好為$\{00, 01, 10, 11\}$,都出現且只出現一次,同樣,迴圈序列$1001$也是一個德布魯因序列。 ![De_Bruijn_sequence.png](https://gitee.com/shinelee/ImgBed/raw/master/2020/德布魯因序列/De_Bruijn_sequence.png) 可見,**德布魯因序列並不唯一,且是個迴圈序列,長度恰好為$k^n$,與所有可能子串的數量相同**。 wiki上的定義如下, > In [combinatorial](https://wiki2.org/en/Combinatorics) [mathematics](https://wiki2.org/en/Mathematics), a **de Bruijn sequence** of order $n$ on a size-$k$ [alphabet](https://wiki2.org/en/Alphabet_(computer_science)) *A* is a [cyclic sequence](https://wiki2.org/en/Cyclic_sequence) in which every possible length-$n$ [string](https://wiki2.org/en/String_(computer_science)#Formal_theory) on $A$ occurs exactly once as a [substring](https://wiki2.org/en/Substring) (i.e., as a *contiguous* [subsequence](https://wiki2.org/en/Subsequence)). Such a sequence is denoted by $B(k, n)$ and has length $k^n$, which is also the number of distinct strings of length $n$ on $A$. > > ——from wiki [De Bruijn sequence](https://wiki2.org/en/De_Bruijn_sequence) 再舉一個$B(2, 4)$的例子,序列長度為$2^4=16$,如下 $$ 0 0 0 0 1 1 1 1 0 1 1 0 0 1 0 1 $$ 其所有迴圈子串如下, ![B(2,4).png](https://gitee.com/shinelee/ImgBed/raw/master/2020/德布魯因序列/B(2,4).png) 每個位置的子串均不相同,所有子串對應著$0\sim 2^n-1$範圍的整數,恰好形成了$2^n$個位置與$2^n$個數的對映。 # 德布魯因序列的使用 將`h`與德布魯因序列相乘,相當於左移操作,把某位置的子串移到了最左端,再將該子串右移至最右,即僅保留該子串,可知道該子串是什麼,因為序列中每個子串的位置都是唯一的,根據對映關係可知道該子串的位置,相當於知道了`h`。為此需要建立 子串與位置 對應關係的檢索表。 ```cpp unsigned int v; int r; static const int MultiplyDeBruijnBitPosition[32] = { 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 }; r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27]; // The index of the LSB in v is stored in r //return the index of the most significant bit set from a 32 bit unsigned integer uint8_t highestBitIndex( uint32_t b ) { static const uint32_t deBruijnMagic = 0x06EB14F9; static const uint8_t deBruijnTable[32] = { 0, 1, 16, 2, 29, 17, 3, 22, 30, 20, 18, 11, 13, 4, 7, 23, 31, 15, 28, 21, 19, 10, 12, 6, 14, 27, 9, 5, 26, 8, 25, 24, }; return deBruijnTable[(keepHighestBit(b) * deBruijnMagic) >> 27]; } ``` 因為德布魯因序列是迴圈序列,而左移操作會自動在最低位填0,所以習慣將全0子串放在序列的最高位,這樣比較方便,不需要特殊處理。 # 德布魯因序列的生成與索引表的構建 德布魯因序列可以通過構建德布魯因圖得到,圖中每條**哈密頓路徑(Hamiltonian path)**都對應一個德布魯因序列, ![De_Bruijn_binary_graph.svg.png](https://gitee.com/shinelee/ImgBed/raw/master/2020/德布魯因序列/De_Bruijn_binary_graph.svg.png) 數量共有 $$ \frac{(k !)^{k^{n-1}}}{k^{n}} $$ 具體生成方式和證明可檢視[De Bruijn sequence](https://wiki2.org/en/De_Bruijn_sequence)和[神奇的德布魯因序列](https://halfrost.com/go_s2_de_bruijn/)。 儲存子串與位置對映關係的檢索表可通過如下方式生成,其中`debruijn32`為德布魯因序列對應的`uint32`正整數。 ```cpp uint8 index32[32] = {0}; void setup( void ) { int i; for(i=0; i<32; i++) index32[ (debruijn32 << i) >> 27 ] = i; } ``` # 參考 - [De Bruijn sequence](https://wiki2.org/en/De_Bruijn_sequence) - [神奇的德布魯因序列](https://halfrost.com/go_s2_de_bruijn/) - [De Bruijn Sequences for Fun and Profit](https://www.slideshare.net/alekbr/de-bruijn-sequences-for-fun-and-profit) - [Bit mathematics cookbook](https://bisqwit.iki.fi/story/howto/b