C++實現LZ77壓縮演算法
阿新 • • 發佈:2019-02-18
LZ77演算法是一種基於詞典壓縮的方法,並且該詞典不是靜態的,而是自適應的動態詞典。LZ77把已經壓縮過的資料當成詞典,未壓縮的資料在已經壓縮的資料中查詢(即動態詞典),然後把偏移量和匹配長度表示出來的一種方法。
介紹LZ77原理的文章很多,這裡就不囉嗦了。其實演算法思路也是比較簡單。本著"紙上得來終覺淺"的觀念,我這裡實現最基本的LZ77的壓縮和解壓演算法,實際上出於壓縮效率的考慮,LZ77的壓縮演算法存在很多的變種。但是理解變種之前,先理解最原始的思路更有幫助,而且知道來龍去脈。如下是一個例子的簡單圖解。
#include <Windows.h> #include <stdio.h> #include "MyLZ77.h" void TestMyLZ77() { //為了方便檢視,使用字串測試,MyLZ77Encode函式是支援二進位制壓縮的 char *pTestStr = "aacaacabcabaaac"; int testStrLen = strlen(pTestStr); PLZ77_ENCODE_LIST header = MyLZ77Encode((unsigned char*)pTestStr,testStrLen);//最後一個位元組看能會多出來,暫時未處理 PLZ77_ENCODE_LIST p = header; if(header) { while(p) { printf("(%2d,%2d,%2c)\r\n",p->item->searchResult->pos,p->item->searchResult->matchLen,p->item->nextChar); p = p->next; } } else { printf("Error: MyLZ77Encode return NULL\r\n"); } char decodeBuf[1024] = {'\0'}; int decodeLen = MyLZ77Decode(header,(unsigned char*)decodeBuf); //最後一個位元組看能會多出來,暫時未處理 if(decodeLen != testStrLen) { printf("Error:Decode len is not match data len before encode.May be add a byte in the end!!!\r\n"); } if(strncmp(pTestStr,decodeBuf,testStrLen)) { printf("Error:Decode data is not match data len before encode\r\n"); } else { printf("Decode Success!!!\r\n"); } } int main(int agrc,char* argv[]) { TestMyLZ77(); getchar(); return 0; }
和圖解的例子一樣,測試結果如下:
#ifndef _MYLZ77_H_ #define _MYLZ77_H_ typedef struct _t_LZ77_ENCODE_SEARCH_RESULT { _t_LZ77_ENCODE_SEARCH_RESULT() { pos = -1; matchLen = 0; } short pos;//字典匹配的起始位置,相對待編碼資料的偏移 short matchLen;//字典匹配的長度 }LZ77_ENCODE_SEARCH_RESULT,*PLZ77_ENCODE_SEARCH_RESULT; typedef struct _t_LZ77_ENCODE_ITEM { PLZ77_ENCODE_SEARCH_RESULT searchResult; unsigned char nextChar;//匹配後的下一個未匹配的字元 }LZ77_ENCODE_ITEM,*PLZ77_ENCODE_ITEM; typedef struct _t_LZ77_ENCODE_LIST { PLZ77_ENCODE_ITEM item; _t_LZ77_ENCODE_LIST* next; }LZ77_ENCODE_LIST,*PLZ77_ENCODE_LIST; //標準的LZ77編碼輸出,這裡使用連結串列儲存編碼的結果 PLZ77_ENCODE_LIST MyLZ77Encode(unsigned char* data,int dataLen); //標準的LZ77解碼輸出,輸入需要編碼後的連結串列,輸出解碼後的位元組數 int MyLZ77Decode(PLZ77_ENCODE_LIST header,unsigned char *pDecodeOut); #endif
#include <Windows.h> #include "MyLZ77.h" #define MY_LZ77_DICTIONARY_LEN 0x06 //動態字典的長度設定為6位元組 #define MY_LZ77_ENCODE_BUFFER_LEN 0x04 //動態編碼的buf長度裝置為4位元組 //更新連結串列 PLZ77_ENCODE_LIST UpdataLZ77Link(PLZ77_ENCODE_LIST *lz77Header,PLZ77_ENCODE_LIST *pCur,PLZ77_ENCODE_ITEM item) { PLZ77_ENCODE_LIST p = new LZ77_ENCODE_LIST; if(!p) { return NULL; } p->item = item; p->next = NULL; if(!*pCur) { *pCur = p; *lz77Header = p; } else { (*pCur)->next = p; *pCur = p; } return p; } //通過m*n的時間複雜度搜尋最長匹配,可以優化該函式 PLZ77_ENCODE_SEARCH_RESULT MyLZ77Search(unsigned char *pDictionary, unsigned short dictionarySize, unsigned char* pEncodeBuffer, unsigned short encodeBufferSize) { PLZ77_ENCODE_SEARCH_RESULT searchResult =(PLZ77_ENCODE_SEARCH_RESULT)new LZ77_ENCODE_SEARCH_RESULT; if(!searchResult) { return NULL; } //在字典中搜索 for(int i=0;i<dictionarySize;i++) { int pos = i; int index = i; for(int j=0;j<encodeBufferSize;j++) { if(pEncodeBuffer[j] == pDictionary[index]) { ++index; continue; } else { break; } } int matchLen = index - i; if(matchLen && matchLen>searchResult->matchLen) { searchResult->pos = dictionarySize-i; searchResult->matchLen = matchLen; } } return searchResult; } //標準的LZ77編碼輸出,這裡使用連結串列儲存編碼的結果 PLZ77_ENCODE_LIST MyLZ77Encode(unsigned char* data,int dataLen) { if(!data || dataLen<=0) { return NULL; } int hasbeenEncodeSize = 0;//已經編碼的資料大小 PLZ77_ENCODE_LIST lz77Header = NULL; PLZ77_ENCODE_LIST pCur = NULL; unsigned short dictionarySize = 0;//開始時字典長度為0 unsigned short encodeBufferSize = MY_LZ77_ENCODE_BUFFER_LEN; if(dataLen<MY_LZ77_ENCODE_BUFFER_LEN) { encodeBufferSize = dataLen; } unsigned char *pDictionary = data; unsigned char *pEncodeBuffer = data; while(encodeBufferSize>0) { PLZ77_ENCODE_SEARCH_RESULT pResult = MyLZ77Search(pDictionary,dictionarySize,pEncodeBuffer,encodeBufferSize); if(!pResult) { break; } PLZ77_ENCODE_ITEM item = new LZ77_ENCODE_ITEM; if(!item) { break; } item->searchResult = pResult; item->nextChar = pEncodeBuffer[pResult->matchLen]; hasbeenEncodeSize += (pResult->matchLen+1);//已編碼的位元組數同步增加 //更新連結串列 if(!UpdataLZ77Link(&lz77Header,&pCur,item)) { break; } //更新動態字典 dictionarySize += pResult->matchLen+1; if(dictionarySize>MY_LZ77_DICTIONARY_LEN) { dictionarySize = MY_LZ77_DICTIONARY_LEN; pDictionary = (pEncodeBuffer+pResult->matchLen+1)-MY_LZ77_DICTIONARY_LEN; } //更新待編碼的buffer指標,往後移動 pEncodeBuffer += (pResult->matchLen+1); //未編碼的資料已經不足MY_LZ77_ENCODE_BUFFER_LEN,更新未編碼的資料,否則未編碼的buffer大小仍然設定為MY_LZ77_ENCODE_BUFFER_LEN if(dataLen-hasbeenEncodeSize<MY_LZ77_ENCODE_BUFFER_LEN) { encodeBufferSize = ((dataLen-hasbeenEncodeSize)>=0)?(dataLen-hasbeenEncodeSize):0; } } return lz77Header; } //標準的LZ77解碼輸出,輸入需要編碼後的連結串列,輸出解碼後的位元組數 int MyLZ77Decode(PLZ77_ENCODE_LIST header,unsigned char *pDecodeOut) { int decodeLen = 0; PLZ77_ENCODE_LIST p = header; unsigned char* pCursorData = pDecodeOut; while(p) { unsigned char *pCopy = pCursorData-p->item->searchResult->pos; for(int i=0;i<p->item->searchResult->matchLen;i++) { *pCursorData = *pCopy; pCursorData++; pCopy++; } *pCursorData = p->item->nextChar; pCursorData++; decodeLen += (p->item->searchResult->matchLen+1); p = p->next; } return decodeLen; }
這個例子可以看到,其實標準的原始LZ77很多時候壓縮比率並不高。而在實際運用中,動態字典長度和待編碼的長度都會比較長,這樣壓縮效果會更好。
我們修過如下:
#define MY_LZ77_DICTIONARY_LEN 32 //動態字典的長度設定為32位元組
#define MY_LZ77_ENCODE_BUFFER_LEN 16 //動態編碼的buf長度裝置為16位元組
測試字串修過為:
char *pTestStr = "abababababababababababababababababababababababababababababab";
那麼,測試結果為: