Programming Assignment 5: Burrows–Wheeler Data Compression
Programming Assignment 5: Burrows–Wheeler Data Compression
1. 題目閱讀
實現(xiàn)Burrows-Wheeler數(shù)據(jù)壓縮算法。這個革命性的算法產(chǎn)生了gzip和pkzip,并且相對容易實現(xiàn),還不受任何專利保護(hù)。它構(gòu)成了unix壓縮實用程序bzip2的基礎(chǔ)。
這個算法由以下三種算法組成:
Burrow-Wheeler變換。Given a typical English text file, transform it into a text file in which sequences of the same character occur near each other many times.(Google翻譯:給定典型的英文文本文件,將其轉(zhuǎn)換為文本文件,其中相同字符的序列在彼此附近多次出現(xiàn)。)
這里讀英文沒讀懂,然后去搜索了一下。**這個變換不會改變字符,但是會改變字符串的順序,它將字符串中相似的字串移動到附近。**在wiki找到個例子,很形象,具體解釋往下面繼續(xù)看。見下表。
| 算法輸入 | SIX.MIXED.PIXIES.SIFT.SIXTY.PIXIE.DUST.BOXES |
| 算法輸出 | TEXYDST.E.IXIXIXXSSMPPS.B..E.S.EUSFXDIIOIIIT |
Move-to-front編碼。給定一個相同字串在附近的文本文件,將其轉(zhuǎn)換為一個出現(xiàn)頻率多的字串在前方的文本。
Huffman壓縮。給定一個出現(xiàn)頻率多的字串
第三步是壓縮信息的一步,因為第一步和第二步產(chǎn)生了一個某些字母比其他字母出現(xiàn)頻繁的文本文件。為了解壓這個文件,用相反的順序操作,第一步,Huffman展開,然后move-to-front解碼,最后Burrow-Wheeler逆變換。你的任務(wù)就是實現(xiàn)Burrow-Wheeler和move-to-front部分。
二進(jìn)制輸輸出
為了讓你的程序可以處理二進(jìn)制數(shù)據(jù),你需要使用算法第四版提供的?BinaryStdIn?和?BinaryStdOut。你可以使用?HexDump?在調(diào)試時來顯示二進(jìn)制輸出。?HexDump?接受一個命令行參數(shù)n,從標(biāo)準(zhǔn)輸入讀取字節(jié),并將其寫入到16進(jìn)制,每行k個十六進(jìn)制數(shù)。
Huffman壓縮和解壓
使用書上提供的
Move-to-front編碼和解碼
move-to-front編碼的主要想法是維護(hù)一個字符的順序。重復(fù)從輸入信息讀取字符,打印出這個字符的位置,然后將這個字符移動到序列的前方。作為一個簡單的例子,如果初始六個字符的順序是A B C D E F,然后我們希望去編碼輸入CAAABCCCACCF,然后我們需要按照如下方法去更新move-to-front序列。
如果輸入中相同的字母出現(xiàn)在相互附近許多次,那么許多輸出值將為很小的證書,例如0,1和2。非常高頻率的確切字母將是Huffman編碼的理想場景。
- Move-to-front編碼。你的任務(wù)是維護(hù)一個包含256個字符的額外ASCII字符串。使用字符在編碼中的順序初始化字符串。然后從輸入中每次讀取一個8位字符,輸出在字符串中的8位索引。當(dāng)c出現(xiàn)過,就將c移動到字符串前面。
- Move-to-front解碼。和編碼初始化相同的字符串,然后從輸入中每次讀取一個八位字符i,將字符串中第i個字符輸出,并且移動到前面。
使用下面的api:
性能需求?編碼解碼時間最差為?~(nR),在實踐中為?~(n + R)。內(nèi)存最差為?~(n + R)。
Circular suffix array.
為了有效的實現(xiàn)Burrows-Wheeler變換中的關(guān)鍵部分,你需要使用一個被叫做?circular suffix array?的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)。這個數(shù)據(jù)結(jié)構(gòu)描述了長度為n的字符串的n個循環(huán)后綴的排序數(shù)組的抽象。作為例子,我們考慮長度為12的字符串?"ABRACADABRA!"。下面的表格顯示了他的12循環(huán)后綴和排序后的結(jié)果。
我們定義了?index[i]?表示在排序后的數(shù)組中,每一行在原始數(shù)組中的位置。例如?index[11] = 2?,表示原始數(shù)組中第二行后綴出現(xiàn)在了排序后數(shù)組的第11行。
實現(xiàn)下面的api,并提供?index[]
邊界條件?參數(shù)args為null,index超出索引。拋出異常。
性能需求?典型的英語文本,空間?~(n + R)?。構(gòu)造函數(shù)?~(nlogn)?,?length()?和?index()?常數(shù)時間。警告:在java7, Update 6中,?substing()?方法使用與字串成比例的時間和空間,換句話說,你不能明確的形成一個?n?個重復(fù)后綴的字符串,因為這樣會花費(fèi)平方時間和空間。(為什么是平方??)
Burrows–Wheeler變換
Burrows–Wheeler變換的目標(biāo)不是去壓縮信息,而是將信息轉(zhuǎn)換為更適合壓縮的形式。變換將輸入字符的順序重新排列為許多有相同字符的集合,但是使用這種變換后的集合依然可以恢復(fù)原始輸入的順序。它取決于下面的直覺:當(dāng)你在英文文本中看到?hen時,它前一個字母最可能為?t?或?w?。如果你能以某種方式將所有這些前面的字母組合起來,你會有一個簡單的數(shù)據(jù)壓縮的機(jī)會。
-
Burrows-Wheeler變換。對一個長度為n的字符串s的Burrows-Wheeler變換定義如下:
考慮對s的n個循環(huán)后綴進(jìn)行排序的結(jié)果,Burrows-Wheeler變換?t[]?是排序后綴的最后一列,?first是原始字符串在的行。還是上面的例子,如下圖。其中?first = 3,t[] = ARD!RCAAAABB。
注意到里面有4個連續(xù)的A,2個連續(xù)的B.這些集合將很容易被壓縮。
% java-algs4 BurrowsWheeler - < abra.txt | java-algs4 edu.princeton.cs.algs4.HexDump 16 00 00 00 03 41 52 44 21 52 43 41 41 41 41 42 42 128 bits同樣,整數(shù)3使用4個子節(jié)來表示(00 00 00 03)。字母?A?被表示為hex?41,字母?R?被表示為?52,其他的以此類推。
-
Burrows-Wheeler逆變換。現(xiàn)在我們來描述如何逆轉(zhuǎn)Burrows-Wheeler變換,并恢復(fù)原始的輸入字符串。如果?jth?原始后綴(將原始字符串向左移動j個字符串)是排序后數(shù)組的第?i?行,我們定義?next[i]?為?(j + 1)th?原始后綴在排序后數(shù)組的行數(shù)。例如,如果?first?是原始字符串所在的行,那么?next[first]?是?1st?原始后綴(將原始字符串向左移動1個字符串)在排序后數(shù)組的行數(shù),?next[next[first]]?是?2nd?原始后綴(將原始字符串向左移動2個字符串)在排序后數(shù)組的行數(shù),以此類推。
-
從給定的?t[],?first?和?next[]?數(shù)組反推信息。Burrows-Wheeler解碼器的輸入是排序后的數(shù)組的最后一列?t[]?和?first。從?t[]?中我們可以推斷出排序后綴數(shù)組的第一行,因為它和?t[]?有相同的字符,只不過是排序的。
當(dāng)給出?next[]?和?first?是,我們可以重構(gòu)原始輸入字符串。因為?ith?原始字符串的第一個字符是原始字符串的第?i?個字符。在上面的例子中,因為?first = 3, 所以我們知道原始字符串在第三行。因此,原始字符串從?A?開始,以?!?結(jié)束。 因為?next[first] = 7,下一個原始字符串后綴出現(xiàn)在第七行,因此下一個原始字符串的字符是?B。因為?next[next[first]] = 11, 下一個原始字符串后綴出現(xiàn)在第11行,因此下一個原始字符串字符是?R。
-
從數(shù)組?t[]?和?first?構(gòu)造數(shù)組?next[]。令人驚訝的是,重構(gòu)這些信息的?next[]?包含在Burrows-Wheeler變換里,也就意味著原始信息也包含在其中。對于只出現(xiàn)過一次的字符,推斷?next[]?是非常容易地。例如,考慮已?C?開頭的后綴字符串。通過查看第一列,看出它在排列后數(shù)組的第8行,排在它后面的下一個后綴字符串將以?C?為結(jié)尾。通過查看最后一列,原始順序的下一個后綴字符串在當(dāng)前數(shù)組的第5行,所以?next[8] = 5。相似的?D?和 *!*也很好得出。
然而,因為?R?出現(xiàn)了2次,next[10] = 1?和?next[4] = 4?或者?next[10] = 4?和?next[4] = 1?就變得含糊不清。 這里有一個關(guān)鍵的規(guī)則來解決模糊性。
如果排序過的數(shù)組第i行和第j行由同樣的字符開始,且 i < j,那么 next[i] < next[j]。
這個規(guī)則意味著next[10] = 1?和?next[4] = 4。為什么這個規(guī)則有效呢?這些行都是被排序過的,也就意味著第10行比第11行字典順序小,因此,剩下10個位置的字符構(gòu)成的字符串,第10行的 也一定小于第11行。我們也知道在由?R?結(jié)尾的兩行中,第1行小于第4行,因此next[10] = 1?和?next[4] = 4,否則這與后綴排序的事實相矛盾。
-
實現(xiàn)BurrowsWheeler類,使用以下api:
性能需求?變換的最差時間?~(n + R),包括構(gòu)造循環(huán)后綴數(shù)組。逆變換時間最差為?~(n + R)。內(nèi)存使用最差?~(n + R)。
2. 題目分析
這回題目對算法實施細(xì)節(jié)講的很細(xì)。畢竟這個壓縮方法課上也沒講。
具體實現(xiàn)MoveToFront, BurrowsWheeler, CircularSuffixArray這三個類。
MoveToFront
編碼?這里需要將字符串中的某一個字符提到第一個,還需要查找某個字符在字符串的第幾個位置。維護(hù)一個長度為R的字符數(shù)組,最開始就按照ascii填充。然后每次從頭開始將前一個元素覆蓋到后一個元素,直到找到匹配的字符,將此字符放到第一個,然后將上一個填充到此位置。
解碼?同樣維護(hù)一個字符數(shù)組,覆蓋到要尋找的第i個。
CircularSuffixArray
構(gòu)造函數(shù)?根據(jù)字符串s的位數(shù)n,生成n個后綴循環(huán)字符串,并在每個字符串最后加上一個表示左移位數(shù)的數(shù),組成一個字符串?dāng)?shù)組,這個數(shù)不會影響字符串的排序,但是可以用來產(chǎn)生index。然后使用MSD對數(shù)組進(jìn)行排序,使用排序后的字符串的最后一位生成index。不用生成t,一是可以通過index生成,二是api沒有需求。
這里題目提示了,不能構(gòu)造n個string,那樣性能就是平方級了。可以在原始字符串上排序。使用了構(gòu)造Array.sort()的比較器。從這里參考到這里。然后最優(yōu)的方法是自己實現(xiàn)一個字符串的三向字符串快速排序。可以參考這里。
長度和index函數(shù)返回對應(yīng)的東西就行。
BurrowsWheeler
編碼?使用循環(huán)后綴數(shù)組,得到index,然后使用index產(chǎn)生?t[]?,t[i] = s?的第?index[i] - 1?位,若?i = 0,就是最后一位。同時記錄?index[i] = 0?的?i,這個就是?first。
解碼?將二進(jìn)制的流轉(zhuǎn)換為?first?和?t[]。然后使用?t[]?去生成?next[]。最后使用?first,?t[], *next[]*來轉(zhuǎn)換為原信息。
逆變換排序要自己寫key-index排序性能才能達(dá)標(biāo)。
發(fā)現(xiàn)好像具體壓縮的程序不需要自己實現(xiàn)。
轉(zhuǎn)載于:https://www.cnblogs.com/huipengly/p/9884670.html
總結(jié)
以上是生活随笔為你收集整理的Programming Assignment 5: Burrows–Wheeler Data Compression的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 花生壳:域名诊断—客户端离线
- 下一篇: AopContext.currentPr