《信息論》的實驗,比較有意思,實驗原理如下:
對於一個通信系統來說,信息傳輸的有效性、可靠性、安全性和認證性是人們的主要目標。其中,信息傳輸的有效性指的是儘可能的使用較短的時間和較少的設備等資源來傳送儘可能多的信息,而這一目的主要是通過信源編碼這個環節來實現的。
雖然有許許多多不同的信源編碼方法,但總的說來,信源編碼主要是通過減少或消除信源的剩餘度來提高傳輸效率的。而且,有時人們爲了追求更高的傳輸效率,在滿足實際需求的情況下,還允許在編譯碼過程中存在一定程度的失真,這就是所謂的有損壓縮。當然,針對不同的應用要求,可以選擇不同的壓縮編碼辦法,爲了方便理解和實現,針對一般的英文文本,可以設計一種半字節壓縮編碼方法來實現數據的壓縮。
(一)有損處理
在一般英文文本中,除了大、小寫英文字母外,還有多種不同的標點符號。爲了達到在不影響文章大意的前提下,儘可能的減少需編碼的符號數,以提高信息傳輸效率的目的,可採取這樣的處理方法:
1) 所有的英文字母不區分大、小寫(如:將所有的大寫英文字母變成小寫字母);
2) 保留標點符號:“,”、“。”、“?”“:”和“ ”;
3) 將“!”和“;”變爲“。”,其他符號全部變成“ ”。
這樣,原來的英文文本就變成了一個新的文本,該文本全部由26個英文字母和“,”、“。”、“?”、“:”以及“ ”這31種符號組成,而且,文章的大意並沒有發生大的變化。可以認爲這種失真是在允許的失真範圍之內的。
(二)數據壓縮
在計算機中,文本文件中的每個符號都是由8位的ASCII碼所構成,共有256種取值的可能。既然經過上述有損處理後文件中只存在31種不同的符號,所以在壓縮編碼過程中只需對31種符號進行編碼,就可以大大壓縮文本文件的數據量。考慮到各字母以及符號出現的概率,並考慮碼字的可分離性,可以採取以下的編碼方法來進行數據的壓縮:
1) 對於概率最大的15個符號分別編以“0000”~“1110”的碼字:
符號 |
碼字 |
符號 |
碼字 |
符號 |
碼字 |
符號 |
碼字 |
空格 |
0000 |
e |
0100 |
l |
1000 |
s |
1100 |
a |
0001 |
f |
0101 |
n |
1001 |
t |
1101 |
c |
0010 |
h |
0110 |
o |
1010 |
u |
1110 |
d |
0011 |
i |
0111 |
r |
1011 |
|
|
2) 對於其餘的16個符號分別編以“1111 0000”~“11111111”的碼字。
符號 |
碼字 |
符號 |
碼字 |
符號 |
碼字 |
符號 |
碼字 |
, |
1111 0000 |
b |
1111 0100 |
m |
1111 1000 |
w |
1111 1100 |
. |
1111 0001 |
g |
1111 0101 |
p |
1111 1001 |
x |
1111 1101 |
? |
1111 0010 |
j |
1111 0110 |
q |
1111 1010 |
y |
1111 1110 |
: |
1111 0011 |
k |
1111 0111 |
v |
1111 1011 |
z |
1111 1111 |
這樣,一些最經常出現的符號從原來的8bit變爲4bit,達到了數據壓縮的目的。
(三)譯碼
譯碼過程是編碼的逆過程,解碼後得到由這31種符號所組成的文本文件。
(四)半字節操作
在計算機中,所有對數據的操作都是以字節(8bit)爲單位的,而我們的編碼與解碼都是以半個字節(4bti)爲單位的,因此需要運用位操作來進行數據的控制:
1)編碼過程中,若待編碼字符爲前15個符號之一,則寫入其4bit碼字;若待編碼字符爲後16個符號之一,則先寫入前4bit“1111”,再寫入後4bit相應的碼字。寫入過程中,由於每次向輸出文件的寫入是以字節(8bit)爲單位的,故需每湊足8bit(1字節)執行一次向輸出文件的寫入操作。
2)譯碼過程中,每次讀取4bit。若不爲“1111”,則根據此次讀取的4bit譯爲相應的前15個符號之一;若爲“1111”,則再次讀取4bit,並根據後4bit譯爲相應的後16個符號之一。讀取過程中,由於每次向輸入文件的讀取是以字節(8bit)爲單位的,故需將每次讀取的8bit(1字節)分爲兩部分:前後各4bit。
代碼如下:
有損壓縮:
#include <stdio.h>
#include <stdlib.h>
void main()
{
FILE *fp, *lp;
char ch;
if((fp = fopen("aaa.txt","r")) == NULL)
{
printf("error!!!\n");
exit(0);
}
if((lp = fopen("bbb.txt","w")) == NULL)
{
printf("error!!!\n");
exit(0);
}
ch = fgetc(fp);
while(ch != EOF)
{
if((ch != '!')&&(ch != ';')&&(ch != ',')&&(ch != '.')&&(ch != '?')&&(ch != ':')
&&((ch<65)||((ch>90)&&(ch<97))||(ch>122)))
ch = ' ';
else if(ch=='!' || (ch==';'))
ch = '.';
fputc(ch,lp);
ch=fgetc(fp);
}
fclose(fp);
fclose(lp);
}
編碼:
#include <stdio.h>
#include <stdlib.h>
static unsigned int outbfr;
static FILE *outfile,*infile;
static int outcnt;
static int outbytecnt;
static int inbytecnt;
void init()
{
outbfr=0;
outcnt=8;
outbytecnt=0;
inbytecnt=0;
}
void putbits(int val)
{
outbfr = (outbfr<<4)&255;
outbfr |= val;
outcnt -= 4;
if (outcnt==0)
{
fputc(outbfr,outfile);
outcnt = 8;
outbytecnt++;
}
}
void alignbits()
{
if (outcnt!=8)
putbits(0);
}
void main()
{
char ch;
int code;
if((infile=fopen("bbb.txt","rb"))==NULL)
{printf("cannot open infile!!!\n");
exit(0);
}
if((outfile=fopen("ccc.txt","wb"))==NULL)
{printf("cannot open outfile!!!\n");
exit(0);
}
init();
ch=fgetc(infile);
while(!feof(infile))
{
inbytecnt++;
switch(ch)
{
case ' ': code=0;break;
case 'a':
case 'A': code=1;break;
case 'c':
case 'C': code=2;break;
case 'd':
case 'D': code=3;break;
case 'e':
case 'E': code=4;break;
case 'f':
case 'F': code=5;break;
case 'h':
case 'H': code=6;break;
case 'i':
case 'I': code=7;break;
case 'l':
case 'L': code=8;break;
case 'n':
case 'N': code=9;break;
case 'o':
case 'O': code=10;break;
case 'r':
case 'R': code=11;break;
case 's':
case 'S': code=12;break;
case 't':
case 'T': code=13;break;
case 'u':
case 'U': code=14;break;
case ',': putbits(15); code=0;break;
case '.': putbits(15); code=1;break;
case '?': putbits(15); code=2;break;
case ':': putbits(15); code=3;break;
case 'b':
case 'B': putbits(15); code=4;break;
case 'g':
case 'G': putbits(15); code=5;break;
case 'j':
case 'J': putbits(15); code=6;break;
case 'k':
case 'K': putbits(15); code=7;break;
case 'm':
case 'M': putbits(15); code=8;break;
case 'p':
case 'P': putbits(15); code=9;break;
case 'q':
case 'Q': putbits(15); code=10;break;
case 'v':
case 'V': putbits(15); code=11;break;
case 'w':
case 'W': putbits(15); code=12;break;
case 'x':
case 'X': putbits(15); code=13;break;
case 'y':
case 'Y': putbits(15); code=14;break;
case 'z':
case 'Z': putbits(15); code=15;break;
default : printf("error\n"); exit(0);
}
putbits(code);
ch=fgetc(infile);
}
alignbits();
fclose(infile);
fclose(outfile);
printf("the compress rate is %d%%\n",(outbytecnt*100)/inbytecnt);
}
解碼:
#include <stdio.h>
#include <stdlib.h>
static unsigned int inbfr;
static FILE *outfile,*infile;
static int incnt;
void init()
{
inbfr=0;
incnt=8;
}
int getbits()
{
int code;
int mask=255;
code=inbfr>>4;
inbfr=(inbfr<<4)&mask;
incnt-=4;
if(incnt==0)
{
inbfr=fgetc(infile);
incnt=8;
}
return code;
}
void main()
{
int code;
char ch;
if((infile=fopen("ccc.txt","rb"))==NULL)
{
printf("cannot open infile!!!\n");
exit(0);
}
if((outfile=fopen("ddd.txt","wb"))==NULL)
{
printf("cannot open outfile!!!\n");
exit(0);
}
init();
inbfr=fgetc(infile);
while(!feof(infile))
{
code=getbits();
if(code!=15)
{
switch(code)
{
case 0: ch=' ';break;
case 1: ch='a';break;
case 2: ch='c';break;
case 3: ch='d';break;
case 4: ch='e';break;
case 5: ch='f';break;
case 6: ch='h';break;
case 7: ch='i';break;
case 8: ch='l';break;
case 9: ch='n';break;
case 10: ch='o';break;
case 11: ch='r';break;
case 12: ch='s';break;
case 13: ch='t';break;
case 14: ch='u';
}
}
else
{
code=getbits();
if(!feof(infile))
{
switch(code)
{
case 0: ch=',';break;
case 1: ch='.';break;
case 2: ch='?';break;
case 3: ch=':';break;
case 4: ch='b';break;
case 5: ch='g';break;
case 6: ch='j';break;
case 7: ch='k';break;
case 8: ch='m';break;
case 9: ch='p';break;
case 10: ch='q';break;
case 11: ch='v';break;
case 12: ch='w';break;
case 13: ch='x';break;
case 14: ch='y';break;
case 15: ch='z';break;
default : printf("error\n"); exit(0);
}
}
}
fputc(ch,outfile);
}
fclose(infile);
fclose(outfile);
system("pause");
}
實驗原理中提到將15個常用字符編寫爲4位(0000~1110),其餘編寫爲8位(11110000~11111111),一次寫文件操作需要8位。看程序之前還在疑惑,4位的字符之後需要等待下一個4位的字符湊齊8位才能進行寫操作,但如果下一個字符是是8位的如何處理,如果繼續等待,解碼之後字符順序就會錯位。結果程序中給出的處理方法是4位字符之後補4個0,雖然是種方法,但有些浪費空間。
程序分爲三部分,每個部分有一個main函數,可以合併成一個文件。