RLE(Run Length Encoding)行程长度压缩算法(也称游程长度压缩算法),是最早出现、也是最简单的无损数据压缩算法。RLE算法的基本思路是把数据按照线性序列分成两种情况:一种是连续的重复数据块,另一种是连续的不重复数据块。对于第一种情况,对连续的重复数据块进行压缩,压缩方法就是用一个表示块数的属性加上一个数据块代表原来连续的若干块数据。对于第二种情况,RLE算法有两种处理方法,一种处理方法是用和第一种情况一样的方法处理连续的不重复数据块,仅仅是表示块数的属性总是1;另一种处理方法是不对数据进行任何处理,直接将原始数据作为压缩后的数据。

为了更直观的说明RLE算法,下面就用示例数据就对RLE算法进行演示。首先是第一种情况,原始数据有5个连续相同的数据块组成:

[block] [block] [block] [block] [block]

则压缩后的数据就是:

[5] [block]

接着是第二种情况,原始数据由连续的不重复数据块组成:

[block1] [block2] [block3] [block4] [block5]

按照第一种处理方法,最后的压缩数据就如以下情形:

[1][block1] [1][block2] [1][block3] [1][block4] [1][block5]

如果按照第二种处理方法,最后的数据和原始数据一样:

[block1] [block2] [block3] [block4] [block5]

数据块block的长度可以是任意长度,数据块长度越长则连续重复的概率就越低,压缩的优势就体现不出来,因此,大多数RLE算法的实现都使用一个字节作为数据块长度。

接下来本文就介绍几种RLE算法的实现,首先是最简单的一种算法实现,这种算法实现对连续的不重复字节采用和重复字节一样的处理方法,就是在每个字节前增加一个值是1的连续块数属性(一个字节)。采用这种处理方法的首先好处是压缩和解压缩算法简单,可以用相同的模式处理两种情况的压缩数据,缺点就是当原始数据重复率比较低时,压缩后的数据长度会超过原始数据的长度,起不到压缩的作用,最糟糕的情况就是所有数据块没有连续重复的情况,压缩后的数据反而膨胀一倍。压缩的过程是这样的,线性扫描原始数据,如果某一个字节后面有重复的字节,则增加重复计数,然后继续向后扫描,直到找到一个不重复的字节,然后将块数和这个字节的数据依次写入压缩数据,然后从新的开始字节继续扫描直到原书数据结束。算法中需要注意的一点就是块数属性是用一个字节存储的,因此最大值就是255,当连续的相同数据超过255个字节时,就从第255个字节处断开,将第256个字节以及256字节后面的数据当成新的数据处理。这种RLE压缩算法的C语言实现如下:

6int Rle_Encode_N(unsigned char *inbuf, int inSize, unsigned char *outbuf, int onuBufSize)

7{

8 unsigned char *src = inbuf;

9 int i;

10 int encSize = 0;

11

12 while(src < (inbuf + inSize))

13 {

14 if((encSize + 2) > onuBufSize) /*输出缓冲区空间不够了*/

15 {

16 return -1;

17 }

18 unsigned char value = *src++;

19 i = 1;

20 while((*src == value) && (i < 255))

21 {

22 src++;

23 i++;

24 }

25 outbuf[encSize++] = i;

26 outbuf[encSize++] = value;

27 }

28

29 return encSize;

30}

对于字符串“AAABBBBBCD”,用这种RLE算法Rle_Encode_N()函数压缩后的数据就是:0x03,0x41,0x05,0x42,0x01,0x43,0x01,0x44共8个字节,比原始长度10个字节少了2个字节,实现了数据长度的压缩。

解压缩的过程也很简单,就是定为到第一个块数属性字节位置,根据块数属性的值n,连续向解压缩缓冲区还原n个原始数据,原始数据就是块数属性后面一个字节的数据,然后偏移到下一个块数属性字节位置继续上述处理,直到压缩数据结束。解压缩算法的C语言实现如下:

32int Rle_Decode_N(unsigned char *inbuf, int inSize, unsigned char *outbuf, int onuBufSize)

33{

34 unsigned char *src = inbuf;

35 int i;

36 int decSize = 0;

37

38 while(src < (inbuf + inSize))

39 {

40 int count = *src++;

41 if((decSize + count) > onuBufSize) /*输出缓冲区空间不够了*/

42 {

43 return -1;

44 }

45 unsigned char value = *src++;

46 for(i = 0; i < count; i++)

47 {

48 outbuf[decSize++] = value;

49 }

50 }

51

52 return decSize;

53}

这个最简单的的RLE算法存在着一个致命问题,就是对连续出现的不重复数据,会因为插入太多块数属性字节而膨胀,如果一段数据连续出现重复数据的情况很少,大多数是连续不重复的数据,则使用上面的算法会导致数据没有被压缩,反而增大了,在极端的情况下,会因为插入的块数属性字节而导致数据膨胀一倍。针对这种情况,人们对算法进行了改进,改进的关键点就是对连续出现的不重复数据,不再简单插入块数属性,而是将其直接作为压缩后的数据处理。这样会遇到一个问题,就是解码的过程中,如何判断一个数据是块数属性数据还是正常的数据?方法就是对块数属性字节设置标志位,将块数属性字节的高两位作为标志位,当这两个标志位是连续的两个1时,则判断此字节数据是块数属性字节,它的剩下的6个位就是重复块数。如果这两个标志位不是连续的1,则认为这个字节是正常数据。现在的问题是,如果用户数据中出现了与标志位冲突的数据怎么办?其实没有太好的方法,解决这个问题的方案就是插入值是1的块数属性字节。往好的方面想,这个改进对于数据不超过192(0xC2)的原始数据是非常有效的,著名的图像文件格式PCX格式,就是使用了这种改进的压缩算法,效果还是不错的。下面就来看看一个改进的压缩算法实现:

55int Rle_Encode_P(unsigned char *inbuf, int inSize, unsigned char *outbuf, int onuBufSize)

56{

57 unsigned char *src = inbuf;

58 int i;

59 int encSize = 0;

60

61 while(src < (inbuf + inSize))

62 {

63 unsigned char value = *src++;

64 i = 1;

65 while((*src == value) && (i < 63))

66 {

67 src++;

68 i++;

69 }

70

71 if((encSize + i + 1) > onuBufSize) /*输出缓冲区空间不够了*/

72 {

73 return -1;

74 }

75 if(i > 1)

76 {

77 outbuf[encSize++] = i | 0xC0;

78 outbuf[encSize++] = value;

79 }

80 else

81 {

82 if((value & 0xC0) == 0xC0)

83 {

84 outbuf[encSize++] = 0xC1;

85 }

86 outbuf[encSize++] = value;

87 }

88 }

89

90 return encSize;

91}

对于字符串“AAABBBBBCD”,用这种RLE算法的Rle_Encode_P()函数压缩后的数据就是:0xC3,0x41,0xC5,0x42,0x43,0x44共6个字节,比原始长度10个字节少了4个字节,对于原始数据都小于192的数据能够有效地抑制因插入块数属性过多导致的数据膨胀。

使用这种算法解压缩,需要判断当前数据是否有块属性标志,如果有则从低6位bit中去到重复数据的块数n,然后将下一个字节的数据重复复制n次。如果当前数据没有块属性标志,则直接使用当前数据,具体实现的C代码见Rle_Decode_P()函数:

93int Rle_Decode_P(unsigned char *inbuf, int inSize, unsigned char *outbuf, int onuBufSize)

94{

95 unsigned char *src = inbuf;

96 int i;

97 int decSize = 0;

98 int count = 0;

99

100 while(src < (inbuf + inSize))

101 {

102 unsigned char value = *src++;

103 int count = 1;

104 if((value & 0xC0) == 0xC0) /*是否有块属性标志*/

105 {

106 count = value & 0x3F; /*低位是count*/

107 value = *src++;

108 }

109 else

110 {

111 count = 1;

112 }

113 if((decSize + count) > onuBufSize) /*输出缓冲区空间不够了*/

114 {

115 return -1;

116 }

117 for(i = 0; i < count; i++)

118 {

119 outbuf[decSize++] = value;

120 }

121 }

122

123 return decSize;

124}

上述优化后的RLE算法,在原始数据普遍大于192(0xC0)的情况下,其优化效果相对于优化前的算法没有明显改善。原因在于,原始的RLE算法和改进后的RLE算法对于连续出现的不重复数据的处理方式都是一个一个处理的,没有把不重复数据作为一个整体进行处理。现在考虑再对原始的RLE算法进行优化,主要优化思想就是对连续的不重复数据进行整理处理,用一个和处理连续重复数据一样的标志,标识后面的数据是长度为n的连续不重复数据。这样的标志字节就相当于是数据块的块头部分,描述后面跟的数据类型以及数据长度。由于标志是始终存在于数据块的前面,因此就不需要区分标志字节和原始数据,也就是说,第一个改进算法中用“高两位是连续的1”的方式区分标志字节和数据都是没有必要的,唯一需要区分的是后面跟的数据类型。区分的方法就是对标志字节的8个bit进行分工,用高位一个bit表示后面跟的数据类型,如果这个bit是1则表示后面跟的是连续重复的数据,如果这个bit是0则表示后面跟的是连续不重复的数据。标志字节的低7位bit存储一个数字(最大值是127),对于连续重复数据,这个数字表示需要重复的次数,对于连续不重复数据,这个数字表示连续不重复数据块的长度。需要注意的是,只有重复次数超过2的数据才被认为是连续重复数据,因为如果数据的重复次数是2,压缩后加上标志字节后总的长度没有变化,因此没有必要处理。

下面根据上述优化思想进行算法设计,首先是压缩算法。在这种情况下,压缩算法就比前两种RLE压缩算法复杂一些,就是要能识别连续的重复数据和连续的不重复数据。首先设置搜索起始位置,算法开始时这个搜索起始位置就是原始数据的第一个字节。每次搜索就是从起始位置开始向后搜索比较数据,根据搜索比较结果,一种情况就是后面数据重复且数据长度超过2,则设置连续重复数据的标志,然后继续向后查找,直到找到第一个与之不相同的数据为止,将这个位置记为下次搜索的起始位置,根据位置差计算重复次数,连重复标志和重复次数以及原始数据一起写入压缩数据;另一种情况是后面的数据都没有连续重复的,则继续向后查找,直到找到连续重复的数据,然后设置不重复数据标志,将新位置记为下次搜索的起始位置,最后将标志字节写入压缩数据并将原始数据复制到压缩数据。从新的搜索起始位置重复上面的过程,直到原始数据结束。函数Rle_Encode_O()就是上述算法的C语言实现(只有算法的主体部分):

185int Rle_Encode_O(unsigned char *inbuf, int inSize, unsigned char *outbuf, int onuBufSize)

186{

187 unsigned char *src = inbuf;

188 int i;

189 int encSize = 0;

190 int srcLeft = inSize;

191

192 while(srcLeft > 0)

193 {

194 int count = 0;

195 if(IsRepetitionStart(src, srcLeft)) /*是否连续三个字节数据相同?*/

196 {

197 if((encSize + 2) > onuBufSize) /*输出缓冲区空间不够了*/

198 {

199 return -1;

200 }

201 count = GetRepetitionCount(src, srcLeft);

202 outbuf[encSize++] = count | 0x80;

203 outbuf[encSize++] = *src;

204 src += count;

205 srcLeft -= count;

206 }

207 else

208 {

209 count = GetNonRepetitionCount(src, srcLeft);

210 if((encSize + count + 1) > onuBufSize) /*输出缓冲区空间不够了*/

211 {

212 return -1;

213 }

214 outbuf[encSize++] = count;

215 for(i = 0; i < count; i++) /*逐个复制这些数据*/

216 {

217 outbuf[encSize++] = *src++;;

218 }

219 srcLeft -= count;

220 }

221 }

222 return encSize;

223}

现在用数据“AAABBBBBCABCDDD”检验上述算法,得到压缩后的数据:0x83,0x41,0x85,0x42,0x04,0x43,0x41,0x42,0x43,0x83,0x44,原始数据长度是15字节,压缩后是11字节,这种改进后的算法,原始数据越长,压缩的效果就越明显。

这种改进方法的解压缩算法就比较简单了,因为两种情况下的数据的首部都有标志,只要根据标志判断如何处理就可以了。首先从压缩数据中取出一个字节的标志字节,然后判断是连续重复数据的标志还是连续不重复数据的标志,如果是连续重复数据,则将标志字节后面的数据重复复制n份,;如果是连续不重复数据,则将连续复制标志字节后面的n个数据。n的值是标志字节与0x3F做与操作后得到,因为标志字节的低7位bit就是数据块数属性。

改进的解压缩算法如下:

225int Rle_Decode_O(unsigned char *inbuf, int inSize, unsigned char *outbuf, int onuBufSize)

226{

227 unsigned char *src = inbuf;

228 int i;

229 int decSize = 0;

230 int count = 0;

231

232 while(src < (inbuf + inSize))

233 {

234 unsigned char sign = *src++;

235 int count = sign & 0x3F;

236 if((decSize + count) > onuBufSize) /*输出缓冲区空间不够了*/

237 {

238 return -1;

239 }

240 if((sign & 0x80) == 0x80) /*连续重复数据标志*/

241 {

242 for(i = 0; i < count; i++)

243 {

244 outbuf[decSize++] = *src;

245 }

246 src++;

247 }

248 else

249 {

250 for(i = 0; i < count; i++)

251 {

252 outbuf[decSize++] = *src++;

253 }

254 }

255 }

256

257 return decSize;

258}

用前面Rle_Encode_O()函数得到的压缩数据进行验证,结果正确。

当今常用的压缩软件普遍使用从LZ77压缩算法改进的LZSS压缩算法。LZSS压缩算法是一种基于字典模型的压缩算法,算法依据是在文本流中词汇或短语很可能会重复出现,同样图像流中图像模式很也可能会重复出现。因此,在处理的过程中,构造一个编码表,用较短的编码代替重复序列,就可以有效地减少数据大小。与LZSS算法相比,RLE的压缩率显然没有LZSS的高,但是RLE算法具有速度快的优势,在LZSS算法出现之前,还是得到了很广泛的应用,比如著名的图像文件格式PCX就是使用了本文提到的第二种RLE算法。

算法系列之八:RLE行程长度压缩算法相关推荐

  1. 简单的程序诠释C++ STL算法系列之八:mismatch

    C++STL的非变易算法(Non-mutating algorithms)是一组不破坏操作数据的模板函数,用来对序列数据进行逐个处理.元素查找.子序列搜索.统计和匹配. mismatch算法是比较两个 ...

  2. RLE行程长度压缩算法

    RLE(Run Length Encoding)行程长度压缩算法(也称游程长度压缩算法),是最早出现.也是最简单的无损数据压缩算法.RLE算法的基本思路是把数据按照线性序列分成两种情况:一种是连续的重 ...

  3. 【算法系列之八】删除链表的倒数第N个节点

    给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点. 示例: 给定一个链表: 1->2->3->4->5, 和 n = 2.当删除了倒数第二个节点后,链表变为 1 ...

  4. BP算法双向传,链式求导最缠绵(深度学习入门系列之八)

    摘要: 说到BP(Back Propagation)算法,人们通常强调的是反向传播,其实它是一个双向算法:正向传播输入信号,反向传播误差信息.接下来,你将看到的,可能是史上最为通俗易懂的BP图文讲解, ...

  5. iOS动画系列之八:使用CAShapeLayer绘画动态流量图

    这篇文章通过使用CAShapeLayer和UIBezierPath来画出一个动态显示剩余流量的小动画. 最终实现的效果如下: Paste_Image.png 动态效果图: shapeLayerAni. ...

  6. 白话经典算法系列之七 堆与堆排序

     堆排序与高速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法.学习堆排序前,先解说下什么是数据结构中的二叉堆. 二叉堆的定义 二叉堆是全然二叉树或者是近似全然二叉树. 二叉堆满 ...

  7. 算法系列之二十:计算中国农历(二)

    (接上篇) 所谓的"天文算法",就是利用经典力学定律推导行星运转轨道,对任意时刻的行星位置进行精确计算,从而获得某种天文现象发生时的时间,比如日月合朔这一天文现象就是太阳和月亮的地 ...

  8. leetcode17. 电话号码的字母组合--每天刷一道leetcode算法系列!

    作者:reed,一个热爱技术的斜杠青年,程序员面试联合创始人 前文回顾: leetcode1. 两数之和--每天刷一道leetcode系列! leetcode2. 两数相加--每天刷一道leetcod ...

  9. 大数据算法系列——布隆过滤器

    大数据算法系列--布隆过滤器 一.简介 Bloom filter介绍 Bloom Filter(BF)是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集 ...

最新文章

  1. Java 8中处理集合的优雅姿势——Stream
  2. NYOJ 685 查找字符串(map)
  3. 文本分类(一)EWECT微博情绪分类大赛第三名Bert-Last_3embedding_concat最优单模型复现
  4. 监视窗口添加 $err,hr 一行来实时现实错误
  5. Linux安全基础:grep命令的使用
  6. mac下使用pyenv
  7. 后端同同不肯给我算好的时间差给我,只好自己写了:
  8. win8+sdk8+vs2012+freeglut+glew开发opengl
  9. 查询相关股票十档行情的方法
  10. cad无法修复图形文件_CAD应用技巧:DWG图形的“瘦身”
  11. Windows下 OpenCV 的下载安装教程(详细)
  12. 斯坦福高效睡眠法-读书笔记
  13. catia曲面扫掠命令详解_4.3.3.15-扫掠曲面之二次曲线_两条引导线扫略
  14. 送书 | 令附生信专用简明 Python 文字和视频教程
  15. 关于如何让模拟器(包括虚拟机哦) 更加流畅
  16. 深度学习应用13电影评论情感分析
  17. 冰河亲自整理的Git命令汇总,悄悄努力,然后惊艳所有人
  18. 【网络技术题库梳理8】网络系统结构与设计的基本原则
  19. Vue 3的设计过程(翻译自尤雨溪原文)
  20. 工业螺旋齿轮行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)

热门文章

  1. 拉格朗日插值与范德蒙矩阵
  2. 全国大学生电子设计竞赛历届题目
  3. 抖音播放量很低,怎么办???
  4. An Introduction to ANCOVA (Analysis of Variance) 协方差分析 看某个treatment排除其他因素后对结果是否有显著性影响
  5. json java 转义_java解析json,带转义字符的json
  6. EF随机从数据库中获取一条数据
  7. MATLAB工具箱导入方法
  8. (转)Mac 给 iPhone 充电一直闪跳 / Mac usb 连接闪动/跳动/时断等情况的解决
  9. Vue后台 - 利用 mockjs 完成数据的获取、编辑、增加、删除和分页【详细步骤篇】
  10. 拒绝平庸:优秀WEB登录页面设计