聊聊字典編碼
最近由於課程設計需要做解壓縮演算法
特此來考察字典編碼
1 導論
許多場合,開始時不知道要編碼資料的統計特性,也不一定允許你事先知道它們的統計特性。因此,人們提出了許許多多的資料壓縮方法,企圖用來對這些資料進行壓縮編碼,在實際編碼過程中以儘可能獲得最大的壓縮比。這些技術統稱為通用編碼技術。
字典編碼(dictionary encoding)技術(以下簡稱DE)就是屬於這一類,這種技術屬於無失真壓縮技術。
- DE根據的是
資料本身包含有重複程式碼
這個特性
例如文字檔案和光柵影像就具有這種特性
1.1 分類
種類很多,歸納起來大致有兩類
1.1.1 查詢正在壓縮的字元序列是否在歷史輸入資料中出現過
用已經出現過的字串替代重複部分,輸出僅僅是指向之前出現過的字串的“指標”
- 這裡的“字典”指 用以前處理過的資料來表示編碼過程中遇到的重複部分
這類編碼的所有演算法都是以abraham lempel
和jakob ziv
在1977年研究發表的稱為lz77
演算法為基礎
1.1.2 從輸入的資料中建立一個“短語字典(dictionary of the phrases)”
這種短語不一定是像“好好學習天天向上”和“你個糟老頭子壞得很我信你個鬼”這類具有具體含義的短語,它可以是任意字元的組合
編碼資料過程中當遇到已經在字典中出現的“短語”時,編碼器就輸出這個字典中的短語的“索引號”,而不是短語本身。
j.ziv
和a.lempel
在1978年首次發表了這種編碼方法的文章
在他們的研究基礎上,terry a.weltch
在1984年發表了改進這種編碼演算法的文章,因此把這種編碼方法稱為LZW(lempel-ziv walch)
壓縮編碼,首先在高速硬碟控制器上應用了這種演算法
2 LZ77演算法
2.1 常見術語
- 輸入資料流(input stream)
要被壓縮的字元序列 - 字元(character)
輸入資料流中的基本單元。 - 編碼位置(coding position)
輸入資料流中當前要編碼的字元位置,指前向緩衝儲存器中的開始字元 - 前向緩衝儲存器(Lookahead buffer)
存放從編碼位置到輸入資料流結束的字元序列的儲存器。 - 視窗(window)
包含W個字元的視窗,字元是從編碼位置開始向後數也就是最後處理的字元數 - 指標(pointer)
指向視窗中的匹配串且含長度的指標
核心是查詢從前向緩衝儲存器開始的最長的匹配串
2.2 執行步驟
1.把編碼位置設定到輸入資料流的起始位
2.查詢視窗中最長的匹配串
3.以“(Pointer, Length) Characters”的格式輸出
其中Pointer是指向視窗中匹配串的指標,Length表示匹配字元的長度,Characters是前向緩衝儲存器中的不匹配的第1個字元。
4.如果前向緩衝儲存器不是空的,則把編碼位置和視窗向前移(Length+1)
個字元,然後返回到步驟2
[例]
- “步驟” 編碼步驟
- “位置” 編碼位置,輸入資料流中的第1個字元為編碼位置1
- “匹配串” 視窗中找到的最長的匹配串
- “字元” 匹配後在前向緩衝儲存器中的第1個字元
-
“輸出” 以“(Back_chars, Chars_length) Explicit_character”格式輸出
- (Back_chars, Chars_length) 指向匹配串的指標
告訴譯碼器在這個視窗中向後退Back_chars
個字元然後拷貝Chars_length
個字元到輸出 - Explicit_character 真實字元
例如,表編碼過程
的輸出(5,2) C
告訴譯碼器回退5個字元,然後拷貝2個字元“AB”
- (Back_chars, Chars_length) 指向匹配串的指標
但wikipedia認為,粗體字理解成從編碼位置開始往回數Back_chars個字元,從該字元開始數起的字串與接下來的Chars_length個字元完全相同
3 LZ78演算法
3.1 術語和符號
- 字元流(Charstream)
要被編碼的資料序列 - 字元(Character)
字元流中的基本資料單元 - 字首(Prefix)
在一個字元之前的字元序列
-綴-符串(String)
字首+字元
- 碼字(Code word)
碼字流中的基本資料單元,代表字典中的一串字元 - 碼字流(Codestream)
碼字和字元組成的序列,編碼器的輸出 - 字典(Dictionary)
綴-符串表。按照字典中的索引號對每條綴-符串(String)指定一個碼字(Code word) - 當前字首(Current prefix)
在編碼演算法中使用,指當前正在處理的字首,用符號P表示。 - 當前字元(Current character)
在編碼演算法中使用,指當前字首之後的字元,用符號C表示。 - 當前碼字(Current code word)
在譯碼演算法中使用,指當前處理的碼字,用W表示當前碼字,String.W表示當前碼字的綴-符串
3.2 編碼演算法
不斷從字元流中提取新的綴-符串(String),通俗地理解為新“詞條”,然後用“代號”也就是碼字(Code word)表示這個“詞條”
這樣一來,對字元流的編碼就變成了用碼字(Code word)去替換字元流(Charstream),生成碼字流(Codestream),從而達到壓縮資料的目的
在編碼開始時字典是空的,不包含任何綴-符串(string)。在這種情況下編碼器就輸出一個表示空字串的特殊碼字(例如“0”)和字元流中(Charstream)的第一個字元C,並把這個字元C新增到字典中作為一個由一個字元組成的綴-符串(string)。在編碼過程中,如果出現類似的情況,也照此辦理。在字典中已經包含某些綴-符串(String)之後,如果“當前字首P +當前字元C”已經在字典中,就用字元C來擴充套件這個字首,這樣的擴充套件操作一直重複到獲得一個在字典中沒有的綴-符串(String)為止。此時就輸出表示當前字首P的碼字(Code word)和字元C,並把P+C新增到字典中作為字首(Prefix),然後開始處理字元流(Charstream)中的下一個字首。
LZ78編碼器的輸出是碼字-字元(W,C)對,每次輸出一對到碼字流中,與碼字W相對應的綴-符串(String)用字元C進行擴充套件生成新的綴-符串(String),然後新增到字典中
具體演算法
步驟1
在開始時,字典和當前字首P都是空的
步驟2
當前字元C:=字元流中的下一個字元
步驟3
P+C 是否在字典中
(1) “是”
用C擴充套件P,讓P := P+C
(2) “否”
① 輸出與當前字首P相對應的碼字和當前字元C
② 把字串P+C 新增到字典中
③ 令P:=空值
(3) 字元流中是否還有字元需要編碼
① “是” 返回到步驟2
② “否” 若當前字首P非空,輸出相應於當前字首P的碼字,然後結束編碼
3.3 譯碼演算法
在譯碼開始時譯碼字典為空,它將在譯碼過程中從碼字流中形成
每當從碼字流中讀入一對碼字-字元(W,C)對時,碼字就參考已經在字典中的綴-符串,然後把當前碼字的綴-符串string
W 和字元C輸出到字元流(Charstream),而把當字首-符串(string.W+C)新增到字典中。在譯碼結束之後,重構的字典與編碼時生成的字典完全相同
具體演算法
步驟1
開始時字典空
步驟2
當前碼字W :=碼字流中的下一個碼字
步驟3
當前字元C := 緊隨碼字之後的字元。
步驟4
把當前碼字的綴-符串(string.W)輸出到字元流(Charstream),然後輸出字元C
步驟5
把string.W+C新增到字典中
步驟6
判斷碼字流中是否還有碼字要譯
(1) “是” 返回到步驟2
(2) “否” 結束
例
●“步驟” 編碼步驟
●“位置” 在輸入資料中的當前位置
●“字典” 新增到字典中的綴-符串,綴-符串的索引等於“步驟”序號
●“輸出” 以(當前碼字W, 當前字元C)簡化為(W, C)的形式輸出
與LZ77相比,LZ78的最大優點是在每個編碼步驟中減少了綴-符串(String)比較的數目,而壓縮率與LZ77類似
4 LZW演算法
使用的術語與LZ78的類似,僅增加了一個術語—字首根(Root),它是由單個字串組成的綴-符串(String)
4.1 編碼原理
- LZW只輸出代表字典中的綴-符串(String)的碼字(code word)
意味著在開始時字典不能是空的,它必須包含可能在字元流出現中的所有單個字元,即字首根(Root) - 由於所有可能出現的單個字元都事先包含在字典中,每個編碼步驟開始時都使用一字元字首(one-character prefix),因此在字典中搜尋的第1個綴-符串有兩個字元
4.2 編碼演算法
LZW編碼是圍繞稱為字典的轉換表
來完成的
這張轉換表用來存放稱為字首(Prefix)的字元序列,並且為每個表項分配一個碼字(Code word),或者叫做序號
這張轉換表實際上是把8位ASCII字符集進行擴充,增加的符號用來表示在文字或影像中出現的可變長度ASCII字串
擴充後的程式碼可用9位、10位、11位、12位甚至更多的位來表示
Welch的論文中用了12位,12位可以有4096個不同的12位程式碼,這就是說,轉換表有4096個表項,其中256個表項用來存放已定義的字元,剩下3840個表項用來存放字首(Prefix)
LZW編碼器就是通過管理這個字典完成輸入/輸出的轉換
- 輸入是字元流(Charstream)
可以是8位ASCII字元組成的字串 - 輸出是用n位(例如12位)表示的碼字流(Codestream)
碼字代表單個字元或多個字元組成的字串。
LZW編碼器使用了一種很實用的分析(parsing)演算法,稱為貪婪分析演算法(greedy parsing algorithm)
在貪婪分析演算法中,每一次分析都要序列地檢查來自字元流(Charstream)的字串,從中分解出已經識別的最長的字串,也就是已經在字典中出現的最長的字首(Prefix)
用已知的字首(Prefix)加上下一個輸入字元C也就是當前字元(Current character)作為該字首的擴充套件字元,形成新的擴充套件字串——綴-符串(String):Prefix.C
這個新的綴-符串(String)是否要加到字典中,還要看字典中是否存有和它相同的綴-符串String
- 如果有,那麼這個綴-符串(String)就變成字首(Prefix),繼續輸入新的字元
- 否則就把這個綴-符串(String)寫到字典中生成一個新的字首(Prefix),並給一個程式碼
演算法的執行
步驟1
開始時的字典包含所有可能的根(Root),而當前字首P是空的
步驟2
當前字元(C) :=字元流中的下一個字元
步驟3
判斷綴-符串P+C是否在字典中
(1) “是” :P := P+C
(2) “否”
① 把代表當前字首P的碼字輸出到碼字流;
② 把綴-符串P+C新增到字典;
③ 令P := C
步驟4
碼字流中是否還有碼字要譯
(1) “是” 回到步驟2
(2) “否”
① 把代表當前字首P的碼字輸出到碼字流
② 結束
LZW編碼演算法可用偽碼錶示
開始時假設編碼字典包含若干個已經定義的單個碼字
4.3 譯碼演算法
- 當前碼字(Current code word)
當前正在處理的碼字,用cW表示,用string.cW表示當字首-符串 - 先前碼字(Previous code word)
先於當前碼字的碼字,用pW表示,用string.pW表示先字首-符串
演算法開始時,譯碼字典與編碼字典相同,包含所有可能的字首根(roots)
在譯碼過程中會記住先前碼字(pW),從碼字流中讀當前碼字(cW)之後輸出當字首-符串string.cW,然後把用string.cW的第一個字元擴充套件的先字首-符串string.pW新增到字典中。
執行步驟
步驟1:在開始譯碼時字典包含所有可能的字首根(Root)。
步驟2:cW :=碼字流中的第一個碼字。
步驟3:輸出當字首-符串string.cW到碼字流。
步驟4: 先前碼字pW := 當前碼字cW。
步驟5: 當前碼字cW := 碼字流中的下一個碼字。
步驟6: 判斷先字首-符串string.pW是否在字典中
(1) 如果“是”,則:
① 把先字首-符串string.pW輸出到字元流。
② 當前字首P :=先字首-符串string.pW。
③ 當前字元C :=當前字首-符串string.cW的第一個字元。
④ 把綴-符串P+C新增到字典。
(2) 如果“否”,則:
① 當前字首P :=先字首-符串string.pW。
② 當前字元C :=當字首-符串string.cW的第一個字元。
③ 輸出綴-符串P+C到字元流,然後把它新增到字典中。
步驟7: 判斷碼字流中是否還有碼字要譯
(1) 如果“是”,就返回到步驟4。
(2) 如果“否”, 結束。
例
- “字典” 新增到字典中的綴-符串,它的索引在括號中
每個譯碼步驟譯碼器讀一個碼字,輸出相應的綴-符串,並把它新增到字典中
例如,在步驟4中,先前碼字(2)儲存在先前碼字(pW)中,當前碼字(cW)是(4),當字首-符串string.cW是輸出(“A B”),先字首-符串string.pW (“B”)是用當字首-符串string.cW (“A”)的第一個字元,其結果(“B A”) 新增到字典中,它的索引號是(6)
LZW演算法得到普遍採用,它的速度比使用LZ77演算法的速度快,因為它不需要執行那麼多的綴-符串比較操作
對LZW演算法進一步的改進是增加可變的碼字長度,以及在字典中刪除老的綴-符串
在GIF影像格式和UNIX的壓縮程式中已經採用了這些改進措施之後的LZW演算法
相關文章
- 聊聊字元編碼字元
- 跟著大彬讀原始碼 - Redis 8 - 物件編碼之字典原始碼Redis物件
- 從一個故事開始聊聊字元編碼字元
- 聊聊領域驅動設計與編碼思想
- 【Redis 系列】redis 學習十六,redis 字典(map) 及其核心編碼結構Redis
- 聊聊二維碼
- 聊聊Vue.js的template編譯Vue.js編譯
- 阿里面試官:HashMap 熟悉吧?好的,那就來聊聊 Redis 字典吧!阿里面試HashMapRedis
- 聊聊程式碼整潔之道
- 來聊聊原始碼學習原始碼
- codevs 4189 字典【字典樹】dev
- mssql生成資料庫字典指令碼-MarkDownSQL資料庫指令碼
- IDEA如何設定編碼格式,字元編碼,全域性編碼和專案編碼格式Idea字元
- 聊聊如何進行程式碼混淆行程
- 字典
- MySQL隱碼攻擊Fuzz過濾字元字典MySql字元
- [Redis原始碼閱讀]dict字典的實現Redis原始碼
- 熵編碼(四)-算術編碼(二)熵
- 聊聊「低程式碼」的實踐之路
- 聊聊原始碼貢獻這件大事原始碼
- 聊聊Dubbo(四):核心原始碼-切入Spring原始碼Spring
- 影像壓縮編碼碼matlab實現——DM編碼Matlab
- 影像壓縮編碼碼matlab實現——行程編碼Matlab行程
- java安全編碼指南之:字串和編碼Java字串
- 字符集編碼(二):字元編碼模型字元模型
- 字串形式的列表,字典轉列表,字典字串
- InnoDB資料字典--字典表載入
- Unicode編碼解碼Unicode
- 影像壓縮編碼碼matlab實現——算術編碼Matlab
- 影像壓縮編碼碼matlab實現——變換編碼Matlab
- 聊聊視訊中的編解碼器,你所不知道的h264、h265、vp8、vp9和av1編解碼庫
- 編碼合集
- java編碼Java
- 字元編碼字元
- DER編碼
- 良好編碼
- 字串-編碼字串
- Emoji 編碼