爲了提升檢索效率,大概有兩種思路:html
[IR] BWT+MTF+AC 中已經介紹了BWT (Burrows–Wheeler_transform)數據轉換算法,算法
這種變換方式不只方便壓縮,同時對pattern search也帶來了意想不到的好處。數組
事實上,BWT形式的數據,能夠僅還原局部數據,而非必須還原完整的文件。分佈式
這個完整的搜索過程叫作:FM-index,包括三部分,①BWT(T),②checkpoint data,③一個簡化了的SA[]數組。post
Left字體
Symbol | #Less Than |
A | 0 |
B | 3 |
N | 4 |
[ | 6 |
] | 7 |
Rightui
Position | Symbol | #Matching(idx) | |
0 | B | 3+0->3:[ | 0 |
1 | N | 0+4->4:A | 0 |
2 | N | 4+1->5:A | 1 |
3 | [ | E | 0 |
4 | A | 0+0->0:B | 0 |
5 | A | 0+1->1:N | 1 |
6 | ] | S->7:A | 0 |
7 | A | 0+2->2:N | 2 |
圖示化以上搜索過程(其中一步Postion:5):編碼
匹配的過程,實際就是搜索範圍逐漸縮小的過程(backward search),以下:url
若能持續搜索到Pattern最後一個字符,則說明該字符串(pattern)在文本中。spa
時間複雜度就是O(len(pattern))。
若使用Run-length FM-Index壓縮後,搜索的過程當中要伴隨着解碼。
Continue: [IR] BWT+MTF+AC
index | Last | B | S | B' | Front | C |
1 | e | 1 | e | 1 | $ | $ |
2 | e | 0 | d | 1 | _ | _ |
3 | d | 1 | _ | 1 | _ | _ |
4 | _ | 1 | n | 1 | a | a |
5 | n | 1 | r | 1 | d | d |
6 | r | 1 | h | 1 | e | e |
7 | r | 0 | t | 0 | e | e |
8 | h | 1 | $ | 1 | e | h |
9 | h | 0 | a | 0 | e | n |
10 | t | 1 | e | 1 | h | r |
11 | $ | 1 | _ | 0 | h | t |
12 | a | 1 | 1 | n | ||
13 | e | 1 | 1 | r | ||
14 | e | 0 | 0 | r | ||
15 | _ | 1 | 1 | t |
若不使用RLFM,本來的搜索方式以下(只考慮Last,Front列)
問題:index:13:e 的下一個是誰?(Notice:這裏Last列與Front列左右位置與一般相反)
過程:
(1)‘右邊’找‘左邊’對應的index
(i) index:13:e 先找‘左邊’e block的首,也就是index:6。
(ii)本身就是‘左邊’e block的3rd e,故,‘左邊’就是index:(6+3-1)=8,
(2)‘左邊「相應的index對應的‘右邊’是誰
(i) 那麼,index:8對應的‘右邊’就是:h
若使用RLFM,搜索方式中伴隨着解碼(需還原出Last,Front列)
【紅色字體部分如今須要解碼才能得出】
(1)‘右邊’找‘左邊’對應的index
(i) index:13:e 先找‘左邊’e block的首,也就是index:6。(block定位)
a: 如何由index:13解碼出e?
index:13經過B列數‘1’(包括本身)得出本身屬於10th block。(index->block)
10th block在S列中就是e。
b: 如何解碼找到‘左邊’e block的首? <--重大區別:e block這裏變爲"block1+block2"
10th block在S列中就是e,且是第二個e(經過數一下前面的,包括本身)。
先 e block定位,故,e在C列 --> 6th block
6th block經過B'列數‘1’得出對應的‘左邊’e1:index:6。 (block->index)
(ii)本身就是‘左邊’e block的3rd e,故,‘左邊’就是index:8,(block內部定位)
a:爲什麼是block中的3rd e?
小block間位移:
第二個e表明:e的block2在C列 --> 7th block
7th block經過B'列數‘1’得出對應的‘左邊’e2:index:8。 (block->index)
求block1->block2的位移:8-6=2
小block間內部位移:
‘右邊’的index=13或者14,在數'1'時(包括本身),前面都是有10個'1'。
index:13所屬block中的1st elem就是:上列中(包括本身)距離本身最近的‘1’的index,即:13
顯然index:13:e距離目前所屬block中的1st e的距離是0(位移),故,本身就是‘e的block2’中的1st。
最終,在‘左邊’e block中的位移:
答:2+1st=3rd e。
b:爲什麼是index:8(在‘左邊’)?
‘左邊’e Block內部定位:e1:index:6 + 3rd - 1 = 8
(2)‘左邊「相應的index對應的‘右邊’是誰
(i) 那麼,index:8對應的右邊就是:h
與(1)(i)(a)同理,
index:8經過B列數‘1’(包括本身)得出本身屬於6th block。(index->block)
6th block在S列中就是h.
(確實比較繞,呵呵呵呵)
由於brute Force太蠢,因此有了該算法。
• Brute force pattern matching runs in time O(mn) in the worst case.
• But most searches of ordinary text take O(m+n), which is very quick.
那麼,剩下的惟一問題就是,如何構造《部分匹配表》(Partial Match Table)
P[j]: The largest prefix of P[0 .. j-1] that is a suffix of P[1 .. j-1].
"部分匹配值"就是"前綴"和"後綴"的最長的共有元素的長度。以"ABACAB"爲例,
[0] ABACAB- P[0 .. -1]的前綴和P[1 .. -1]的後綴爲「非法」,共有元素的長度爲-1;
[1] ABACAB- P[0 .. 0]的前綴和P[1 .. 0]的後綴爲空,共有元素的長度爲0;
[2] ABACAB- P[0 .. 1]的前綴爲{A},P[1 .. 1]的後綴爲空,共有元素的長度爲0;
[3] ABACAB- P[0 .. 2]的前綴爲{A, AB},P[1 .. 2]的後綴爲{A},共有元素的長度爲1;
[4] ABACAB- P[0 .. 3]的前綴爲{A, AB, ABA},P[1 .. 3]的後綴爲{AC, C},共有元素的長度爲0;
[5] ABACAB- P[0 .. 4]的前綴爲{A, AB, ABA, ABAC},P[1 .. 4]的後綴爲{ACA, CA, A},共有元素的長度爲1;
但,也有缺陷:
KMP doesn’t work so well as the size of the alphabet increases
– more chance of a mismatch (more possible mismatches)
– mismatches tend to occur early in the pattern, but KMP is faster when the mismatches occur later
算是一種改進形式,跟重視後綴;頭部對齊,從尾部比較。
Most text processors use BM for 「find」 (&「replace」) due to its good performance for general text documents.
Ref: 字符串匹配的Boyer-Moore算法
Link: http://www.cs.utexas.edu/users/moore/publications/fstrpos.pdf
特色:《好字符規則》和《壞字符規則》,以最大移動值爲準。
一個簡單的示例:
Step 1
首先,"字符串"與"搜索詞"頭部對齊,從尾部開始比較。
這是一個很聰明的想法,由於若是尾部字符不匹配,那麼只要一次比較,就能夠知道前7個字符(總體上)確定不是要找的結果。
咱們看到,"S"與"E"不匹配。這時,"S"就被稱爲"壞字符"(bad character),即不匹配的字符。
咱們還發現,"S"不包含在搜索詞"EXAMPLE"之中,這意味着能夠把搜索詞直接移到"S"的後一位。以下:
Step 2
依然從尾部開始比較,發現"P"與"E"不匹配,因此"P"是"壞字符"。
可是,"P"包含在搜索詞"EXAMPLE"之中。因此,將搜索詞後移兩位,兩個"P"對齊。(利用了pattern內部的信息)
這個兩位是怎麼來的呢?
Ans:《壞字符規則》
後移位數 = 壞字符的位置 - 搜索詞中的上一次出現位置
OK,根據這個規則,再從新審視Step1 and Step2。
Step 1: 後移位數=6-(-1)=7 // -1:在pattern中未發現壞字符
Step 2: 後移位數=6-4=2 // 4:在pattern中idx=4發現壞字符
However,這樣是不夠的,在某種狀況下還不能達到更優的移動策略。
繼續咱們的示例:
Step 1
依然從尾部開始比較,"E"與"E"匹配;接下來,匹配了更多。
比較前面一位,"MPLE"與"MPLE"匹配。咱們把這種狀況稱爲"好後綴"(good suffix),即全部尾部匹配的字符串。
注意,"MPLE"、"PLE"、"LE"、"E"都是好後綴。
但接下來,繼續比較前一位,發現"I"與"A"不匹配。因此,"I"是"壞字符"。
根據"壞字符規則",此時搜索詞應該後移 2 - (-1)= 3 位。以下:
但,看上去這個move不是很聰明的樣子,顯然能夠一次性移動更多步。
初步看上去,並無利用到Pattern中兩次出現的E。
如何利用?
Ans:《好後綴規則》
後移位數 = 好後綴的位置 - Pattern中的上一次出現位置
OK,根據這個規則,再從新審視Step1。
Step 1: 後移位數=6-0=6 // 0:"好後綴"(MPLE、PLE、LE、E)之中[Ref:KMP"部分匹配表"],只有"E"在"EXAMPLE"出如今頭部,idx=0
- "好後綴"的位置以最後一個字符爲準。假定"ABCDEF"的"EF"是好後綴,則它的位置以"F"爲準,即5(從0開始計算)。
- 若是"好後綴"在搜索詞中只出現一次,則它的上一次出現位置爲 -1。也就是pattern靠前的位置沒有再出現了呢。
- 若是"好後綴"有多個,
- 最長的那個"好後綴",位置靈活;靠前位置出現的話,優先選!不然,查看其餘「好後綴」。
- 其餘"好後綴",上一次出現位置必須在頭部。
好比,假定"BABCDAB"的"好後綴"是"DAB"、"AB"、"B",這時"好後綴"的上一次出現位置是什麼?
BABCDAB
BABCDAB
BABCDAB <----
回答是,此時採用的好後綴是"B",它的上一次出現位置是頭部,即第0位。
這個規則也能夠這樣表達:若是最長的那個"好後綴"只出現一次,則能夠把搜索詞改寫成以下形式進行位置計算"(DA)BABCDAB",即虛擬加入最前面的"DA"。
更巧妙的是,這兩個規則的移動位數,只與搜索詞有關,與原字符串無關。所以,能夠預先計算生成《壞字符規則表》和《好後綴規則表》。使用時,只要查表比較一下就能夠了。
那麼,如何事前製表?
Ref: http://www.cs.utexas.edu/users/moore/publications/fstrpos.pdf
補充:簽名文件索引(Signature File Index)
做爲一種經常使用的索引組織方式,它在不少領域獲得了應用。下面從存儲和查詢兩個階段對它進行介紹。
1.存儲階段
對於每一個關鍵字,分配一個固定大小的向量(k-bit),這個向量叫作簽名(Signature);
對於一個網頁文件,通過詞典切分後,造成由對應關鍵字序列構成的向量,即P=<key1,key2,…,keym>,對這些關鍵字的簽名作OR運算,就造成了網頁文件的簽名。這個過程也被稱爲重疊編碼(Superimposed Coding)。
簽名(Signature) = k-bit OR k-bit OR k-bit OR ...
而後把網頁文件的簽名結果依次存入一個個獨立的文件中,造成對應的簽名文件,這樣造成的簽名文件比原文件小不少。(大小不就取決於k了麼?)
例如:有一頁網頁分詞後有這樣一些關鍵字「文本」、「英語」、「單詞」、「信件」,假設將這些關鍵字經某哈希表散列成固定位的數字向量(以6位爲例),分別爲:
hash(文本)= 000110,
hash(英語)= 110001,
hash(單詞)= 001101,
hash(信件)= 000111,
這些數字向量即爲關鍵字的簽名,而後將這些簽名作OR運算,獲得網頁文件的簽名。
2.查詢階段
接受用戶查詢語句Q,首先把用戶查詢串字符串切分紅關鍵字序列,造成查詢向量,即A=<key1,key2,…,keyn>。
而後把關鍵字映射成相應的向量簽名,再與網頁簽名文件進行按位與運算,獲得最後的匹配結果。
3.優缺點
簽名文件索引方式是一種比較有效的索引機制,文件組織簡單,基本和原文件順序一致;
維護容易,生成、插入、刪除都很方便;
所需空間小,特別是採用重疊編碼以後;
實現比較簡單,更新比較容易;
適合並行處理和分佈式存儲。
可是簽名向量的大小選擇是一個須要研究的問題,並且對於大的文本文件,必須進行分塊處理,檢索速度慢,須要順序掃描。