基本原理
该段来自互联网和witten发表的论文《Arithmetic Coding for Data Compression》。
算术编码的基本原理是将编码的数据表示成实数0和1之间的一个间隔(Interval),数据越长,编码表示它的间隔就越小,表示这一间隔所需的二进制位就越多。算术编码也是一种熵编码的方法。和其它熵编码方法不同的地方在于,其他的熵编码方法通常是把输入的数据分割为符号,然后对每个符号进行编码,而算术编码是直接把整个输入的数据编码为一个数,一个满足(0.0<=n<1.0)的小数n,本质上是一种块编码技术。
算术编码用到两个基本的参数:符号的概率和它的编码间隔。信源符号的概率决定压缩编码的效率,也决定编码过程中信源符号的间隔,而这些间隔包含在0到1之间。编码过程中的间隔决定了符号压缩后的输出。
给定事件序列的算术编码步骤如下:
(1)编码器在开始时将“当前间隔” [ L, H) 设置为[0,1)。
(2)对每一事件,编码器按步骤(a)和(b)进行处理
(a)编码器将“当前间隔”分为子间隔,每一个事件一个。
(b)一个子间隔的大小与下一个将出现的事件的概率成比例,编码器选择子间隔对应于下一个确切发生的事件相对应,并使它成为新的“当前间隔”。
(3)最后输出的“当前间隔”的下边界就是该给定事件序列的算术编码。
算术编码也是一种对错误很敏感的编码方法,如果有一位发生错误就会导致整个消息译错。
算术编码可以是静态的或者自适应的。在静态算术编码中,信源符号的概率是固定的。在自适应算术编码中,信源符号的概率根据编码时符号出现的频繁程度动态地 进行修改,在编码期间估算信源符号概率的过程叫做建模。需要开发动态算术编码的原因是因为事先知道精确的信源概率是很难的,而且是不切实际的。当压缩消息 时,我们不能期待一个算术编码器获得最大的效率,所能做的最有效的方法是在编码过程中估算概率。因此动态建模就成为确定编码器压缩效率的关键。
CACM87算法及实现
在看了互联网上的介绍后,尝试着对CACM87进行实现。编码阶段尚能理解,但是解码部分可能由于描述的问题或是我自身理解的问题没有怎么看懂,因而花了很久才弄明白,这也是我发表这篇博文的原因。如果博文中有雷同或是相同的地方,还请原著者见谅。下面给出编码和解码的伪代码。
编码阶段:
for every symbol in string
do
range=high-low;//high和low是当前编码间隔的上届和下界,初始值high=1.0,low=0.0。
high=low+range*symbol.lowvalue;//symbol及待编码的字符,symbol.lowvalue表示该字符对应的字间隔的下界,如A这个字符的间隔为[0.1,0.2),则A.lowvalue=0.1,相应的A.highvalue=0.2
low=low+range*symbol.highvalue;
end-do
end-for
解码阶段:
do
ret=(lockvalue-low)/(high-low);//lockvalue间于编码完成后的low和high之间,且在解码过程中保持不变。ret用确定即将输出字符所在的间隔,假设ret=0.12,A的间隔为[0.1,0.2)。则应该输出A。high和low的初始值为1.0和0.0。
output symbol;//输出对应的字符
Range=high-low;
high=low+range*symbol.valuehigh;
low=low+range*symbol.valuelow;
while there is nothing to decode;
为了演示这两个算法,我假设了如下的情况:
假设{A,B,C,D,E,F}出现的概率分别为{0.1,0.2,0.2,0.1,0.1,0.3},根据这些概率,可以将间隔[0,1)分割成6个子间隔:[0,0.1),[0.1,0.3),[0.3,0.5),[0.5,0.6),[0.6,0.7),[0.7,1)。其对应的表为:
字符表 | 出现概率 |
A | [0,0.1) |
B | [0.1,0.3) |
C | [0.3,0.5) |
D | [0.5,0.6) |
E | [0.6,0.7) |
F | [0.7,1) |
编码过程:
编码字符 | 对应字间隔 | 信息格式 | 编码后表示的字符串 |
A | [0,0.1) | <0,0.1> | A |
B | [0.1,0.3) | <0.01,0.03> | AB |
B | [0.1,0.3) | <0.012,0.016> | ABB |
D | [0.5,0.6) | <0.014,0.0144> | ABBD |
F | [0.7,1) | <0.01428,0.0144> | ABBDF |
F | [0.7,1) | <0.014364,0.0144> | ABBDFF |
E | [0.6,0.7) | <0.0143856,0.0143892> | ABBDFFE |
C | [0.3,0.5) | <0.0143867,0.0143874> | ABBDFFEC |
解码过程:
解码子间隔 | 对应输出 | 子间隔包含的信息 | 输出后形成的间隔 |
0< interval <0.1 | A | BBDFFEC | <0,0.1> |
0.1< interval <0.3 | B | BDFFEC | <0.01,0.03> |
0.1< interval <0.3 | B | DFFEC | <0.012,0.016> |
0.5< interval <0.6 | D | FFEC | <0.014,0.0144> |
0.7< interval <1 | F | FEC | <0.01428,0.0144> |
0.7< interval <1 | F | EC | <0.014364,0.0144> |
0.6< interval <0.7 | E | C | <0.0143856,0.0143892> |
0.3< interval <0.5 | C | NULL | <0.0143867,0.0143874> |
为了演示这两个过程,我编写了一段很粗糙的程序。体现了当初我迫切弄清解码算法的心情,如果有仁兄不幸看了这篇博文,请勿喷我。
#include<iostream>
using namespace std;
void main()
{
unsigned int length;
double high=1.00000000000;
double low=0.000000000000;
double value=0.0;
double range=1;
int i=0;
int j=0;
double valuehigh;
double valuelow;
double ret;
cout<<"input the length of a string"<<endl;//输入要编码字符串的长度
cin>>length;
cout<<"input the string"<<endl;
char ch;
for(j=0;j<length;j++)
{
cin>>ch;
if(ch=='A')
{
valuehigh=0.1;
valuelow=0.0;
}
else if(ch=='B')
{
valuehigh=0.3;
valuelow=0.1;
}
else if(ch=='C')
{
valuehigh=0.5;
valuelow=0.3;
}
else if(ch=='D')
{
valuehigh=0.6;
valuelow=0.5;
}
else if(ch=='E')
{
valuehigh=0.7;
valuelow=0.6;
}
else if(ch=='F')
{
valuehigh=1.0;
valuelow=0.7;
}
range=high-low;
high=low+range*valuehigh;
low=low+range*valuelow;
}
cout<<"(low,high)=("<<low<<","<<high<<")"<<endl;
high=1.00000000;
low=0.00000000;
cout<<"input a number between low and high"<<endl;//输入间于low和high的一个数。
cin>>value;
cout<<"";
while(i++<length)
{
ret=(value-low)/(high-low);
if(ret>=0.0&&ret<0.1)
{
cout<<"A";
valuehigh=0.1;
valuelow=0.0;
}
else if(ret>=0.1&&ret<0.3)
{
cout<<"B";
valuehigh=0.3;
valuelow=0.1;
}
else if(ret>=0.3&&ret<0.5)
{
cout<<"C";
valuehigh=0.5;
valuelow=0.3;
}
else if(ret>=0.5&&ret<0.6)
{
cout<<"D";
valuehigh=0.6;
valuelow=0.5;
}
else if(ret>=0.6&&ret<0.7)
{
cout<<"E";
valuehigh=0.7;
valuelow=0.6;
}
else if(ret>=0.7&&ret<1.0)
{
cout<<"F";
valuehigh=1.0;
valuelow=0.7;
}
cout<<","<<valuelow<<"< interval <"<<valuehigh<<",";
range=high-low;
high=low+range*valuehigh;
low=low+range*valuelow;
cout<<"<"<<low<<","<<high<<">"<<endl;
}
cout<<endl;
}
后记:
后来我发现,随着字符串长度的增加,high和low会出现相等的情况,这时算法就失去了作用,不过有专门处理这种情况的文章。由于当时精力有限,没有做深入研究,再此也请有心人将心得与大家分享,万分感激。语言粗糙,敬请见谅。