算术编码wiki简介
http://zh.wikipedia.org/wiki/%E7%AE%97%E6%9C%AF%E7%BC%96%E7%A0%81
具体做法的步骤我在网上找到个很不错的PPT
这里也放上来
用算术编码方法是将被编码的一个消息或一个符号串(序列)表示成0和1之间的一个间隔,即对一串符号直接编码成[0,1)区间上的一个浮点小数,在传输任何符号串(消息)之前,设符号串的完整范围为[0,1)。当一个符号被处理时,这一范围就依据分配给这一符号的那一范围变窄,间隔变小,当符号串序列越长,则编码表示它的间隔越小,同时表示这一间隔所需的位数就越多,直到完成对所有符号串的编码。算术编码的过程,实际上就是依据信息源符号串的发生概率对码区间分割的过程。
编码过程
假如要对有10个符号的信息源发出的字符串“state tree”进行编码,符号串具有如下的概率分布如图所示。
按每个符号出现的概率对[0,1)区间进行划分,显然每个符号都有一对应的子区间,这里所用的10个字符被分配的范围如图 所示。
按对‘state stree”’的算术编码过程为:
(1)初始化时,被分割的范围 range=high-low=[0,1) = 1 - 0 = 1,下一个范围的低、高端分别由下式计算:
low=low+range×range low
high=low+range×range high
其中等号右边的low为上一个被编码字符的范围低值;range low和range high分别为本次被编符号已给定出现的概率范围的low和high。
(2)对消息第1字符s编码:s的range low=0.60,s的range high=0.70,因此下一个区间的low和high为:
low=low+range×range low=0+1×0.6=0.6
high=low+range×range high=0+1×0.7=0.7
range=high一low=0.7一0.6=0.1
S将区间[0,1) [0.6,0.7)
注意:字符“”表示“分割为”字符。
(3)对第2个字符t编码,使用的新生成范围为[0.6,0.7),因为t的range low=0.70,range high=1.00,因此下一个low,high分别为:
low=low+range×range low=0.6+0.1×0.7=0.67
high=low+range×range high=0.6+0.1×1.0=0.70
range=high一low=0.7一0.67=0.03
t将区间[0.6,0.7) [0.67,0.70 )
(4)对第3个字符 a编码,在新生成的[0.67,0.70)中进行分割。因为 a的 range low=0.10,range high=0.2,因此下一个low,high分别为:
low=low+range×range low=0.67+0.03×0.1=0.673
high=low+range×range high=0.67+0.03×0.2=0.676
range=high一low=0.676一0.673=0.003
a将区间[0.67,0.7 ) [0.673,0.676 )
(5)对第4个字符t编码,在新生成的[0.673,0.676 )上进行分割。因为 t的 range low=0.70,range high=1.00,因此下一个low,high分别为:
low=low+range×range low
=0.673+0.003×0.7=0.6751
high=low+range×range high
=0.673+0.003×1.0=0.676
range=high一low=0.676一0.6751=0.0009
t将区间[0.673,0.676 )[0.6751,0.676)
同理得到下面各字符e , ,t ,r ,e ,e 编码所得到的范围分别为:
[0.67528,0.67555),
[0.67528,0.675307),
[0.6752989,0.675307),
[0.67530295,0.67530376),
[0.675303112,0.675303355)
[0.6753031606,0.6753032335)。
将编码后的区间范围综合如图所示:
我们用0.6753031606代表字符串“state tree”,从而达到高效编码的目的,这就是算术编码的基本思想。
上述算术编码区间分割过程可用下图表示。
解码过程
通过编码,最后一个子区间的的下界值0.6753031606就是字符串“state tree”的惟一编码。然后在解码时,通过判断哪一个字符能拥有我们已编码的消息落在的空间来找出消息中的第一个字符。由于0.6753031606落在[0.6,0.7]之问,因此马上就可解得第1个符号是S。
在解出S后,由于我们知道S的范围的上界和下界,利用编码的逆作用,首先除掉S的下界值0.6,得0.075303606,然后用s的范围range=0.1去除所得到的0.0753031606,得到0.753031606,接着找出0.753031606所落在的区间[0.7,1.0)。就可解得第2个符号是t。
去掉t的下界值0.67,得0.0053031606,然后用t的range=0.03除0.0053031606,得到0.17677202,找出0.17677202所属范围的字符a,
如此继续解码操作,就可以获得字符串“state tree”的准确译码。
简单的模拟这个过程,我取了a-j这10个字母,只编码这么多,每个概率假设相同都为1/10。用简单的double型数据来存储编码后的结果,调试的时候发现double型太不精确了,导致了误差很大,很失败,后来换用java的double来写也是一样的,看来用这个是不行的,由于double的不准确导致连基本的编码前的字符位数都不能确定,所以就编码之后加上了一个位数以至于编码后的大小不在0-1之间,不过我也只是个模拟而已,所以懂得这个道理就行了。
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<malloc.h>
/**
编码 (a-j)
**/
double BianMa(char *s)
{
int len=strlen(s);
int i;
double low;
//编码第一个字符
low=(*s-'a')*0.1;
//编码剩下字符
for(i=1;i<len;i++)
{
low=low+((*(s+i)-'a')*0.1)*pow(0.1,i);
}
return low+len;
}
/**
解码
**/
char* JieMa(double s)
{
int temp,i,len;
char *str;
len=(int)s;//源码的长度
s=s-len;
str=(char*)malloc(sizeof(char)*len);
for(i=0;i<len;i++)
{
s=s*10;
temp=(int)s;
if(i==len-1) temp=temp+1;//纠正double带来的误差,效果不好
*(str+i)=temp+'a';
s=s-temp;
}
*(str+i)='\0';
return str;
}
int init()
{
int c;
char *str;
double s;
str=(char*)malloc(50);
printf("==============================算术编码==============================\n");
printf("仅支持编码abcdefghij这些字符,");
printf("请选择\n1.编码\n2.解码\n3.退出\n");
scanf("%d",&c);
getchar();
switch(c)
{
case 1:
printf("请输入字符(a-j):\n");
scanf("%s",str);
s=BianMa(str);
printf("编码后为:%lf\n\n",s);
getchar();
init();
break;
case 2:
printf("请输入待解码码字:\n");
scanf("%lf",&s);
str=JieMa(s);
printf("解码为:%s\n\n",str);
getchar();
init();
break;
case 3:
return 0;
default:
printf("请正确输入\n\n");
getchar();
init();
break;
}
}
int main()
{
init();
return 1;
}