信息论课程设计报告

更新时间:2024-05-01 06:28:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

成绩:

教师姓名:

1. 任务说明

2016-2017学年第1学期

《信息论》课程设计

学院名称: 班级学号: 学生姓名:

2016年12月

一、判定唯一可译码

输入:任意的一个码(即已知码字个数及每个具体的码字) 输出:判决结果(是/不是)

输入文件:in1.txt,含至少2组码,每组的结尾为”$”符 输出文件:out1.txt,对每组码的判断结果

说明:为了简化设计,可以假定码字为0,1串

2. 实现原理

判断方法:将码C中所有码字可能的尾随后缀组成一个集合F,当且仅当集合F中没有 包含任一码字,则可判断此码C为唯一可译变长码。

构成集合F:首先观察码C中最短的码字是否是其他码字的前缀。若是,将其所有可能 的尾随后缀排列出。就是将其他码字序列中截去与其最短码字相同的前缀 部分,将余下的序列为尾随后缀。而这些尾随后缀又可能是某些码字的前 缀,或者最短码字又仍是这些尾随后缀的前缀,再将由这些尾随后缀产生 的新的尾随后缀列出。然后再观察这些新的尾随后缀是否是某些码字的前 缀,或观察有否其他码字是这些新的尾随后缀的前缀,再将产生的尾随后 缀列出,依次下去,直至没有一个尾随后缀是码字的前缀或没有新的尾随 后缀产生为止。这样,首先获得的是由最短码字能引起的所有尾随后缀。 接着,按照上述步骤将次短的码字、......所有码字可能产生的尾随后缀前部 列出。由此得到由码C的所有可能的尾随后缀组成的集合F。 参考算法伪代码:

For all Wi,Wj?C do

if Wi是Wj的前缀 then

将相应的后缀作为一个尾随后缀放入集合F0中 End if End for Loop

For all Wi?C do

For all Wj?Fn do

if Wi是Wj的前缀 then

将相应的后缀作为一个尾随后缀放入集合Fn?1中 Else if Wj是Wi的前缀 then

将相应的后缀作为一个尾随后缀放入集合Fn?1中 End if End for End for F??iFi

If ?Wi?F,Wi?C then

Return false

Else if F 中未出现新的元素 then

Return true End if

//能走到这里,说明F中有新的元素出现,需继续

End loop

3. 实现源码

#include #include #include #include usingnamespace std;

#pragmawarning(disable:4996) char c[100][50]; //保存码字 char f[300][50]; //保存尾随后缀

int N, sum = 0; //N为码字的个数,sum为尾随后缀个数 int flag; //判断是否唯一可译标志位

//检测尾随后缀

void patterson(char c[], char d[]) { int i, j, k; for (i = 0;; i++) { If (c[i] == '\\0'&&d[i] == '\\0')//两字符串一样长,跳出 break; if (c[i] == '\\0') //d比c长,将d的尾随后缀放入f中 { for (j = i; d[j] != '\\0'; j++) f[sum][j - i] = d[j]; f[sum][j - i] = '\\0'; for (k = 0; k

if (strcmp(f[sum], f[k]) == 0) /*查看当前生成的尾随后缀在f集合中 是否存在*/ { sum--; break; } } sum++; break; } if (c[i] != d[i])//字符不一样了也退出(前缀不同) break; } }

void main(){ int k = 0, N = 0, m = 0, a[50], z = 0; a[m] = N; m++; fstream file1; file1.open(\); //码字读取 FILE *file; file = fopen(\, \); int num = fgetc(file) - 48; for (int n = 0; n < num; n++){ int i = 0, j; if (fgetc(file) == ' ') N += (fgetc(file) - 48); else N += (fgetc(file) - 48); a[m] = N; m++; fgetc(file); for (k; k < N; k++){ for (int q = 0;; q++){ char temp = fgetc(file); c[k][q] = temp; if (temp == ' ' || temp == '$'){ c[k][q] = '\\0'; break; } } } //生成尾随后缀 flag = 0; for (i = a[z]; i

for (j = i + 1; j

}

}

{ flag = 1; break; } } } if (flag == 1) { for (int y = a[z]; y < N; y++) file1 << c[y] <<' '; file1 <<\不是唯一可译码。\\n\; } else{ for (int y = a[z]; y < N; y++) file1 << c[y] <<' '; file1 <<\是唯一可译码。\\n\; } }

file1 <<\尾随后缀集合为:\; for (i = 0; i < sum; i++) file1 << f[i] <<' '; file1 <<\; z++; sum = 0;

4. 运行结果

输入文件:in1.txt

说明:输入文件中第一个数字表示码的组数,第二个数字表示一组码码字的个数,一组码结束以“$”符号结尾;“$”符号后的数字表示下一组码的码字个数。此例以两组码输入为例,多组码判断同上。

输出文件:out1.txt

结果分析:程序首先读取第一组码,进行是否唯一可译码的判断,在输出文件第一行输出判断结果,在第二行输出该码字产生的尾随后缀集合(若只是输出是否唯一可译码的判断结果,不能完全说明程序的正确性,可能存在偶然性;若是输出的尾随后缀集合是正确的,则能说明程序的正确性,由于选取的两组数据来自课本,可以准确的知道尾随后缀集合是否正确,则可验证此程序的正确性,即可用于判断码是否为唯一可译码)。

5. 设计体会

通过此实验的设计,进一步加深了我对唯一可译码判别方法的理解。此实验在设计完成的过程中出现两大难点,第一点就是,作为此程序的核心,两个码字生成尾随后缀的函数编写,选取两个字符数组保存码字和后缀,通过码字长度和单个字符比较来生成尾随后缀;第二个难点是码字的文件读取,起初考虑的是整个码字一起读取,发现实现过程较为复杂,经过修改,改为单个字符读取能简化程序的设计。其他部分按照唯一可译码的判断方法进行设计,关键部分调用尾随后缀生成函数即可,再将判断结果输出到输出文件。此实验总体而言较为简单,实现时注意细节、没有逻辑误区即可。

二、游程编码+Huffman码

1. 任务说明

要求:一无记忆二元信源,0符号的出现概率为1/4, 1符号的出现概率为3/4。现要求 对该信源连续出现的n个符号序列,先进行游程编码,再对结果进行Huffman 编码。然后,再进行Huffman译码和游程译码。假定,连续出现的0或1序列 的长度不超过16,n不小于256。 输入:长为n的0/1串

输出:1. 游程编码结果,2. Huffman编码结果,3. Huffman译码结果 4. 游程译码结果 输入文件:in2.txt,含至少两组输入

输出文件:out2.txt,对每组输入的处理结果

2. 实现原理

游程编码:信源输出的字符序列中各种字符连续地重复出现而形成一段一段的字符串, 这种字符串的长度称为游程。游程编码(RLC)就是将信源输出的这种字符 序列映射成由串的字符,串的长度和串的位置三个标志组成的标志序列。知 道了串的字符、串的长度和串的位置的标志序列,就可以完全恢复出原来的 字符序列。

在二元序列中,只有两种符号,即“0”和“1”,这些符号可连续出现,连 “0”这一段称为“0”游程,连“1”这一段称为“1”游程。它们的长度分 别称为游程长度L(0)和L(l)。“0”游程和“l”游程总是交替出现的。如果规 定二元序列是以“0”开始,第一个游程是“0”游程,第二个必为“1”游 程,第三个又是“0”游程等等。对于随机的二元序列,各游程长度将是随 机变量,其取值可为1,2,3,?,直到无限。

Huffman码:(1)将q个信源符号按概率分布P(si)的大小,以递减次序排列起来, 设p1>=p2>=p3>=...>=pq

(2)用0和1码符号分别分配给概率最小的两个信源符号,并将这两个

概率最小的信源符号合并成一个信服好,并用这两个最小概率之和 作为新符号的概率,从而得到只包含q-1个服啊后的新信源,称为 S信源的缩减信源S1。

(3)把缩减信源S1的符号仍按概率大小以递减次序排列,再将其最后两 个概率最小的符号合并成一个新符号,并分别用0和1码符号表示, 这样又形成了q-2个符号的缩减信源S2。

(4)依次继续下去,直至缩减信源最后只剩两个符号为止。将这最后两 个符号分别用0和1码符号表示。最后这两个符号的概率之和必为 1。然后从最后一级缩减信源开始,依编码路径由后向前返回,就得 出各信源符号所对应的码符号序列,即得对应的码字。

Huffman码参考算法伪代码: if q=2 then Return Else

s0?0,s1?1

降序排序

{pi}

缩减信源:创建一个符号s'以取代递归调用Huffman算法以得到布为

sq?1,sq?2,其概率的编码

p'?pq?2?pq?1

(相应的概率分

s0,s1,...,sq?3,s'w0,w1,...,wq?3,w'p0,p1,...,pq?3,p')

Return End if

s0?w0,s1?w1,...,sq?3?wq?3,sq?2?w'0,sq?1?w'13. 实现源码

#include #include #include #include #include

#pragmawarning(disable:4996) #define N 100 #define M 2*N-1

typedefchar * HuffmanCode[2 * M];//haffman编码 usingnamespace std; char x[100]; char y[100];

fstream in(\); fstream out(\);

void youchengbianma(char a[100]) { char yc[100]; int i = 0, j = 0, jishu = 1; yc[0] = a[0]; for (i = 0; a[i] != '\\0'; i++) { if (a[i] == a[i + 1]) jishu++; else { yc[j + 1] = jishu + 48; j = j + 2; yc[j] = a[i + 1]; jishu = 1; }

} yc[j] = '\\0'; cout <<\游程编码后:\<< yc << endl; strcpy_s(x, yc); }

void youchengyima(char a[100]) { char bz = 0, jieya[100], j, jishu; for (int i = 0; a[i] != '\\0'; i = i + 2) { jieya[bz] = a[i]; for (j = bz, jishu = 1; jishu <= a[i + 1] - 48; jishu++, j++) jieya[j] = a[i]; bz = j; } jieya[j] = '\\0'; cout <<\游程译码后:\<< jieya << endl; out <<\游程译码后:\<< jieya << endl; }

typedefstruct { int weight;//权值 int parent;//父节节点 int LChild;//左子节点 int RChild;//右子节点

}HTNode, Huffman[M + 1];//huffman树 typedefstruct Node { int weight; //叶子结点的权值 char c; //叶子结点 int num; //叶子结点的二进制码的长度 }WNode, WeightNode[N];

/***产生叶子结点的字符和权值***/

void CreateWeight(char ch[], int *s, WeightNode CW, int *p) { int i, j, k; int tag; *p = 0;//叶子节点个数 //统计字符出现个数,放入CW for (i = 0; ch[i] != '\\0'; i++) { tag = 1; for (j = 0; j

{ tag = 0; break; } if (tag) { CW[++*p].c = ch[i]; CW[*p].weight = 1; for (k = i + 1; ch[k] != '\\0'; k++) if (ch[i] == ch[k]) CW[*p].weight++;//权值累加 } } *s = i;//字符串长度 }

/********创建HuffmanTree********/

void CreateHuffmanTree(Huffman ht, WeightNode w, int n) { int i, j; int s1, s2; //初始化哈夫曼树 for (i = 1; i <= n; i++) { ht[i].weight = w[i].weight; ht[i].parent = 0; ht[i].LChild = 0; ht[i].RChild = 0; } for (i = n + 1; i <= 2 * n - 1; i++) { ht[i].weight = 0; ht[i].parent = 0; ht[i].LChild = 0; ht[i].RChild = 0; } for (i = n + 1; i <= 2 * n - 1; i++) { for (j = 1; j <= i - 1; j++) if (!ht[j].parent) break; s1 = j; //找到第一个双亲为零的结点 for (; j <= i - 1; j++) if (!ht[j].parent) s1 = ht[s1].weight>ht[j].weight ? j : s1;

ht[s1].parent = i; ht[i].LChild = s1; for (j = 1; j <= i - 1; j++) if (!ht[j].parent) break; s2 = j; //找到第二个双亲为零的结点 for (; j <= i - 1; j++) if (!ht[j].parent) s2 = ht[s2].weight>ht[j].weight ? j : s2; ht[s2].parent = i; ht[i].RChild = s2; ht[i].weight = ht[s1].weight + ht[s2].weight;//权值累加 } }

/***********叶子结点的编码***********/

void CrtHuffmanNodeCode(Huffman ht, char ch[], HuffmanCode h, WeightNode weight, int m, int n) { int i, c, p, start; char *cd; cd = (char *)malloc(n*sizeof(char)); cd[n - 1] = '\\0';//末尾置0 for (i = 1; i <= n; i++) { start = n - 1; //cd串每次从末尾开始 c = i; p = ht[i].parent;//p在n+1至2n-1 while (p) //沿父亲方向遍历,直到为0 { start--;//依次向前置值 if (ht[p].LChild == c)//与左子相同,置0 cd[start] = '0'; else//否则置1 cd[start] = '1'; c = p; p = ht[p].parent; } weight[i].num = n - start; //二进制码的长度(包含末尾0) h[i] = (char *)malloc((n - start)*sizeof(char)); strcpy(h[i], &cd[start]);//将二进制字符串拷贝到指针数组h中 } free(cd);//释放cd内存 system(\); }

/*********所有字符的编码*********/

void CrtHuffmanCode(char ch[], HuffmanCode h, HuffmanCode hc, WeightNode weight, int n, int m) { int i, k; for (i = 0; i

/*****解码*****/

void TrsHuffmanTree(Huffman ht, WeightNode w, HuffmanCode hc, int n, int m) { int i = 0, j, p; while (i

/*****释放huffman编码内存*****/

void FreeHuffmanCode(HuffmanCode h, HuffmanCode hc, int n, int m) { int i; for (i = 1; i <= n; i++)//释放叶子结点的编码 free(h[i]); for (i = 0; i

int n; /*n为叶子结点的个数*/

*/

int m; /*m为字符串ch[]的长度*/ Huffman ht; /*Huffman二叉数*/

HuffmanCode h, hc; /*h存放叶子结点的编码,hc 存放所有结点的编码*/ WeightNode weight; /*存放叶子结点的信息*/ void huffmanbm(char *ch) { n = 0; int i; m = 0; printf(\); CreateWeight(ch, &m, weight, &n); /*产生叶子结点信息,m为字符串ch[]的长度 printf(\); for (i = 1; i <= n; i++) //输出叶子结点的字符与权值 printf(\, weight[i].c); printf(\); for (i = 1; i <= n; i++) printf(\, weight[i].weight); CreateHuffmanTree(ht, weight, n); /*产生Huffman树*/ printf(\); printf(\); for (i = 1; i <= 2 * n - 1; i++) //打印Huffman树的信息 printf(\, i, ht[i].weight, ht[i].parent, ht[i].LChild, ht[i].RChild); CrtHuffmanNodeCode(ht, ch, h, weight, m, n); /*叶子结点的编码*/ printf(\); /*打印叶子结点的编码*/ for (i = 1; i <= n; i++) { printf(\, weight[i].c); printf(\, h[i]); } CrtHuffmanCode(ch, h, hc, weight, n, m); /*所有字符的编码*/ printf(\编码后\); /*打印字符串的编码*/ for (i = 0; i < m; i++) { printf(\, hc[i]); strcpy(&y[i], hc[i]); } system(\); }

void huffmanyima() { printf(\译码后:\); TrsHuffmanTree(ht, weight, hc, n, m); /*解码*/

FreeHuffmanCode(h, hc, n, m); system(\); }

int main() { char s[100]; if (!in.is_open()) { cout <<\; exit(1); } while (!in.eof()) { in.getline(s, 100); cout << s << endl; youchengbianma(s); out <<\游程编码后:\<< x << endl; huffmanbm(x); out <<\编码后:\<< y << endl; out <<\译码后:\; huffmanyima(); youchengyima(x); } in.close(); out.close(); return 0;

}

4. 运行结果

输入文件:in2.txt

输出文件:out2.txt

结果分析:对n个符号序列进行游程编码后输出“0”游程长度和“1”游程长度,再对结果进行霍夫曼编码,得到游程编码结果的霍夫曼码。然后进行译码,首先的是霍夫曼译码,得到游程编码结果,再进行游程译码,得到原符号序列。第二组符号序列编码译码过程相同。

5. 设计体会

游程编码相对简单,首先计算每次连续相同字符的个数,然后将每次连续相同的字符及个数保存起来,再使用霍夫曼编码。霍夫曼编码用概率匹配方法进行信源编码,有两个明显的特点:一是霍夫曼码的编码方法保证了概率大的符号对应于短码,概率小的符号对应于长吗,充分利用了短码;二是缩减信源的最后两个码字总是最后一位不同,从而保证霍夫曼编码是即时码。完成哈夫曼的编码首先建立哈夫曼树,定义当前节点的字符,当前节点的左子、右子和父亲指针,之后对所有字符根据权重进行编码(先在所有可能出现的字符中筛选出当前权重最小的两个字符,将这两个字符分别作为新节点的左子和右子建立一个小的二叉树,并将两个字符的权重之和赋值给新节点,将新二叉树放入筛选字符中,再将筛选过的两个字符从筛选列表中淘汰掉。依次对列表中剩下的字符进行权重最小的筛选,直到根节点),最后再对文件内容进行编码。

三、算术编码的编码与译码

1. 任务说明

要求:输入字符集为{a,b},且p(a)=1/4,p(2)=3/4.对长度L<=30的序列进行算术编码,并进

反向译码

输入文件:in3.txt,含至少两组输入,每组为满足要求的串 输出文件:out3.txt,对每组输入的编码和译码结果

2. 实现原理

算术编码用到两个基本的参数:符号的概率和它的编码间隔。信源符号的概率决定压缩编码的效率,也决定编码过程中信源符号的间隔,而这些间隔包含在0到1之间。编码过程中的间隔决定了符号压缩后的输出。

给定事件序列的算术编码步骤如下:

(1)编码器在开始时将“当前间隔” [ L, H) 设置为[0,1)。 (2)对每一事件,编码器按步骤(a)和(b)进行处理

(a)编码器将“当前间隔”分为子间隔,每一个事件一个。

(b)一个子间隔的大小与下一个将出现的事件的概率成比例,编码器选择子间隔 对应于下一个确切发生的事件相对应,并使它成为新的“当前间隔”。 (3)最后输出的“当前间隔”的下边界就是该给定事件序列的算术编码。

3. 实现源码

#include #include

#pragmawarning(disable:4996) usingnamespace std; #include\

char S[100], A[10]; //用户的码字、保存输入字符 float P[10], f[10], gFs; //字符概率 char bm[100], jm[100]; fstream in(\); fstream out(\);

//编码程序

void bianma(int a, int h) { int i, j; float fr; float ps = 1; float Fs = 0; float Sp[100];

//以待编码的个数和字符个数为循环周期将待编码的字符所对应的概率存入到Fs中 for (i = 0; i

fr = f[j];//将划分好的[0,1)区间的对应点赋值给fr } } Fs = Fs + ps*fr;//从选择的子区间中继续进行下一轮的分割。不断的进行这个 过程直到所有符号编码完毕。 ps *= Sp[i]; //求Ps }

cout <<\<< Fs << endl;//显示最终的算术编码 gFs = Fs;

out <<\算术编码结果:\<< endl; out <<\<< Fs << endl;

float l = log((float)1 / ps) / log((float)2);//计算算术编码的码字长度l if (l>(int)l)l = (int)l + 1;

else l = int(l);//将Fs转换成二进制的形式 int d[20]; int m = 0; while (l>m) { Fs = 2 * Fs; if (Fs>1) { Fs = Fs - 1; d[m] = 1; } elseif (Fs<1)d[m] = 0; else { d[m] = 1; break; } m++; }

int z = m;

//解决有关算术编码的进位问题,给二进制数加 if (m >= l) { while (1) { d[m - 1] = (d[m - 1] + 1) % 2;//最后位加 if (d[m - 1] == 1)break; else m--; } }

cout <<\; out <<\;

for (int e = 0; e < z; e++) { cout << d[e];

}

out << d[e]; }

cout << endl; out << endl;

//解码程序

void jiema(int a, int h) { int i; float Ft, Pt; float Fs = 0, Ps = 1; out <<\算数译码后:\; for (i = 0; i-1; j--) { Ft = Fs; Pt = Ps; Ft += Pt*f[j];//对进行逆编码 Pt *= P[j]; if (gFs >= Ft)//对其进行判断并且将值存入到数组A中 { Fs = Ft; Ps = Pt; cout << A[j]; out << A[j]; break; } } } cout << endl; out << endl; }

void main() { cout <<\编码个数\; int a, i, h = 0; in >> a; cout << a << endl; cout <<\输入编码符号和概率值\<< endl; for (i = 0; i < a; i++) {

}

char x; float y; in >> x; cout << x; A[i] = x;//将字符依次存入数组A中 in >> y; cout << y << endl; P[i] = y;//将字符所对应的概率依次存入到数组P中 }

for (i = 1; i < a; i++) { f[0] = 0; f[i] = f[i - 1] + P[i - 1];//将要编码的数据映射到一个位于[0,1)的实数区间中 }

while (!in.eof()) { cout <<\输入需要编码的符号序列,同时用$结尾\<< endl; h = 0; while (1)//这个while语句的作用是将要编码的字符存入到数组S中 { char cc; in >> cc; if (cc == '$') break;//在以“$”为结尾的时候结束存储 S[h++] = cc; cout << cc; } cout << endl; cout <<\算术编码结果:\<< endl; bianma(a, h); cout <<\对应的解码结果:\<< endl; jiema(a, h); system(\); }

in.close(); out.close();

4. 运行结果

输入文件:in3.txt

说明:第一行表示信源符号个数;第二行表示第一个信源符号及其概率,下一行表示第二个信源符号及其概率(若不止两个信源符号也可写入输入文件)。接下来为待编码的码符号序列,每一组以“$”符号结尾。

输出文件:out3.txt

结果分析:对每一组码符号序列输出算术编码的结果,包括累积分布函数和码字,再对该码字进行算术译码,得到原符号序列(观察是否与原来的一致)。因为符号序列在这里较短,压缩的效果不够明显。

5. 设计体会

在课程学习时对于算术编码没有去详细地了解它的编码方法以及实现,在做这个实验的时候,通过一点点地去看去设计,终于对算术编码有了深入的了解。算术编码的设计更多的用到数学计算,通过实现累计分布函数和信源符号序列所对应区间宽度即可完成算术编码的核心部分,依据书上给出的递推公式,实现难度不是很大,得到累积分布函数后还要依据计算出的长度l取其小数点后l位,还要考虑进位的问题。有了编码的铺垫,译码则显得容易的多,进行逆编码即可。在设计的过程中,可以发现算术编码效率很高,编译码的速度也很快。

输入文件:in3.txt

说明:第一行表示信源符号个数;第二行表示第一个信源符号及其概率,下一行表示第二个信源符号及其概率(若不止两个信源符号也可写入输入文件)。接下来为待编码的码符号序列,每一组以“$”符号结尾。

输出文件:out3.txt

结果分析:对每一组码符号序列输出算术编码的结果,包括累积分布函数和码字,再对该码字进行算术译码,得到原符号序列(观察是否与原来的一致)。因为符号序列在这里较短,压缩的效果不够明显。

5. 设计体会

在课程学习时对于算术编码没有去详细地了解它的编码方法以及实现,在做这个实验的时候,通过一点点地去看去设计,终于对算术编码有了深入的了解。算术编码的设计更多的用到数学计算,通过实现累计分布函数和信源符号序列所对应区间宽度即可完成算术编码的核心部分,依据书上给出的递推公式,实现难度不是很大,得到累积分布函数后还要依据计算出的长度l取其小数点后l位,还要考虑进位的问题。有了编码的铺垫,译码则显得容易的多,进行逆编码即可。在设计的过程中,可以发现算术编码效率很高,编译码的速度也很快。

本文来源:https://www.bwwdw.com/article/a1qg.html

Top