BWT算法,實質上是前綴樹的一種實現。那么什么是前綴樹呢?
一、前綴樹
對於問題p in S?如果S=rpq,那么p為S前綴rp的一個后綴。
於是,為了判斷p in S 是否成立,我們找到S的所有前綴,然后逐一判斷p是不是它們的后綴。為了加快效率,我們將所有的前綴建成一顆樹,這棵樹便是前綴樹。下面,我們舉例說明前綴樹的建立過程和如何使用前綴樹進行模式匹配。
前綴樹的建立
假設S='acaacg',p='aac',那么我們首先找到S的所有前綴,如下
- a
- ac
- aca
- acaa
- acaac
- acaacg
於是,我們將這些前綴翻轉過來,然后建立為一顆字典樹,如下圖
模式匹配
\(p='aac'\),令\(p'=caa\)(即p的翻轉)。顯然,現在只需進行一次樹的搜索,即可完成匹配。
如果在判斷p in S 的同時,還需要得到p 在S 中的位置,那么只需在建樹的時候,將每個字符的索引加上,例如
當然,也可以不保存索引,每次模式匹配結束時,沿着當前節點走下去,一直到為S[0]。
在節點中添加數字,有其他用處,詳見我的另一篇博文廣義后綴樹的簡介。
評價
我們可以看到,相對於常規的匹配算法,前綴樹時間復雜度比較小,但占用空間較大。下面要說的BWT算法,就是解決這個問題的。
二、構建BWT(S)
仍然,以S='acaacg'為例。
1.令S1=S+'$'='acaacg$';
2.循環左移S1 6次,得到S2,S3,S4,S5,S6,S7;
3.對S1到S7按字典序排序(\(\$\)字符的字典序最小),取每個串的最后一個字符,連成一個序列\('gc\$aaac'\)。於是為\(BWT(S)='gc\$aaac'\)。
也許,到這里,你還不清楚BWT變換和前綴樹,有什么關系。那就接着往下看吧。
三、使用BWT,進行模式匹配
我們已經知道BWT(S)=\('gc\$aaac'\),對BWT(S)中的字符進行排序得到\(S'='\$aaaccg'\),得到下圖形式的矩陣。
這個矩陣看起來,有些規律,但是又很奇怪。下面通過復原S的過程,我們來理解以下這個矩陣。
復原S
這個過程用語言描述比較麻煩,直接看圖
按照圖中(1)到(7)步,我們即可得到'$gcaaca',於是S='acaacg'。
其中,斜線表示是,我們找到最后一列的某個符號,然后跳至這個符號在第一列的位置。比如,在第(3)步中,最后一列為第2個c,我們跳到第一行中第2個c的位置。
模式匹配
p='aac',令\(p'='caa'\),選取c作為起點,由於S中有兩個c,因此有兩種可能 的匹配。
- 從第一個c出發
- 從第二個c出發
因此,在方案2得到p',因此p in S是正確的。
幾個問題
1.問題一:如何得到某個符號,在本列中是第幾個?
顯然,我們可以使用一個數組來保存。例如,對於'$gcaaca',數組a=[1,1,1,1,2,2,3]。
$ g c a a c a
[1,1,1,1,2,2,3]
但是,還有一種省空間的辦法。我們只保存串'$gcaaca'中某些字符的位置,這些字符我們稱為checkpoint。
2.問題二:如何得到模式p在S中的位置?
匹配模式串之后,繼續運行,直至$,但是這樣比較耗時。
另一種辦法,在BWT串中記錄相應的偏移。這種辦法空間開銷比較大,也可以采取類似於checkpoint的方法,記錄部分的偏移。
四、待研究的問題
- 如何快速得到一個串的BWT編碼?
- 如何允許部分匹配?
題外話
DNA比對還有一類快速的辦法——使用哈希。