C++实现LZ77压缩算法

     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";

      那么,测试结果为:

      

     

  • 3
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值