p=p->next;
++j;
}/*寻找第i-1个结点位置*/
if(!
p||j>i-1)returnNULL;/*在l中确定插入位置,p=NULL或j>i-1时,即插入位置不合法*/
if(!
(s=(dulinklist)malloc(sizeof(dulnode))))returnNULL;/*动态生成新结点失败,则返回错误*/
s->data=e;/*给新结点的数据域赋值*/
s->next=p->next;
p->next->prior=s;
s->prior=p;
p->next=s;/*在双向链表中插入新结点时指针的变化*/
return0;
}
dulinklistlistdelete_dul(dulinklist&l,inti,int&e)/*在双向链表l中,删除第i个结点*/
{
dulinklistp;
p=l->next;/*p指向第一个结点*/
intj=1;/*j表示结点位置*/
while(p&&(j
p=p->next;
++j;
}/*寻找第i个结点*/
if(!
p||j>i)returnNULL;/*在l中确定第i个元素的位置指针p,p=NULL,即第i个元素不存在*/
e=p->data;/*把指针p的数据域的值赋给e*/
p->prior->next=p->next;
p->next->prior=p->prior;/*在双向链表中删除结点时指针的变化*/
free(p);/*把结点p删掉*/
return0;
}
intlocateelem_dul(dulinklistl,inte)/*查找双线链表中第一个值为e的结点位置*/
{
dulinklistp;
intj;
p=l->next;/*p指向第一个结点*/
j=1;/*j表示结点位置*/
while((p->data!
=e)&&p){
p=p->next;
++j;
}/*寻找第一个值为e的结点位置*/
if(!
p)
return-1;/*返回第一个值为e的结点位置*/
else
return1;
}
voidprint_dul(dulinklistl)/*打印双向链表l*/
{
dulinklistp;
p=l;/*p指向头结点*/
while(p){
printf("%d",p->data);
p=p->next;
}/*把双向链表中的数据都打印出来*/
}
voidsave_dul(dulinklistl)
{
FILE*fp;
if((fp=fopen("H:
\\test2.txt","w+"))==NULL)
{
printf("cannotopenfile!
\n");
exit(0);
}
while(l)
{
fprintf(fp,"%d",l->data);
l=l->next;
}
}
voidmain(){
dulinklistl;
inti,e,x;
intpos;
create_dul(l);/*调用双向链表建立函数*/
print_dul(l);/*调用双向链表的打印函数*/
while
(1){
printf("inputxtochoose(1.查找,2.插入,3.删除,4.0ver):
\n");
scanf("%d",&x);
switch(x){
case1:
printf("\npleaseinputthedatayouwanttolocate:
\n");
scanf("%d",&e);/*输入要查找的值*/
pos=locateelem_dul(l,e);/*调用双向链表的查找函数*/
if(pos==-1)printf("Thedataisnotfound!
\n");
elseprintf("Thedataisfound!
\n");break;
case2:
printf("pleaseinputthepositionyouwanttoinsert:
\n");
scanf("%d",&i);/*输入要插入的位置*/
printf("pleaseinputthedatayouwanttoinsert:
\n");
scanf("%d",&e);/*输入新结点的数据*/
listinsert_dul(l,i,e);/*调用双向链表的插入函数*/
print_dul(l);break;/*调用双向链表的打印函数*/
case3:
printf("\npleaseinputthepositionyouwanttodelete:
\n");
scanf("%d",&i);/*输入要删除结点的位置*/
listdelete_dul(l,i,e);/*调用双向链表的删除函数*/
print_dul(l);break;/*调用双向链表的打印函数*/
case4:
save_dul(l);
exit(0);
}
}
}
●5.程序运行结果
●6.总结:
通过此次数据结构的课程设计,我对程序的编程,编译,执行等有了更深的认识。
理解到思路对于一个程序的重要性,在整个设计过程中,遇到了很多不同的问题,但通过尝试,努力和老师的帮助最终都解决了,感到很有成就感。
从中,我意识到程序的成功不仅仅是消除语法上的错误,而且还要执行成功。
缺乏完整的思路,尽管语法正确,程序还是无法执行。
数据结构的设计考验的不仅仅是我们对书本知识的理解,还考验了我们分析事物时思维的逻辑紧密性,加深并巩固了我对数据结构的认识。
总的来说,这次的数据结构课程设计加深了我对数据结构的认识,也学到了相关的知识,并且巩固了课堂上所学的知识,还锻炼了实践的应用操作能力,感受颇深,也收获了成功的喜悦。
一、《哈夫曼编码器》课程设计
实验报告
设计目的
1、利用学过的数据结构知识设计一个简单的哈夫曼编/译码器系统。
2、了解并掌握数据结构与算法的设计方法,具备初步的独立分析和设计能力;
3、初步掌握软件开发过程的问题分析、系统设计、程序编码、测试等基本方法和技能;
4、提高综合运用所学的理论知识和方法独立分析和解决问题的能力;
5、训练用系统的观点和软件开发一般规范进行软件开发,培养软件工作者所应具备的科学的工作方法和作风。
2
需求分析
2.1、选题的意义和背景
利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。
但是,这是要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数据进行译码(复原)。
对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。
2.2、基本要求
(1)初始化:
键盘输入字符集大小n、n个字符和n个权值,建立哈夫曼树;
(2)编码:
利用建好的哈夫曼树生成哈夫曼编码;
(3)输出编码;
(4)设字符集及频度如下表:
字符
ABCDEFGHIJKLM
频度
1866413223210321154757153220
字符
NOPQRSTUVWXYZ
频度
5763151485180238181161
2.3、运行环境及开发工具
本程序采用VisualC++6.0编程实现。
3
概要设计
3.1设计思想
本程序的主要功能是实现对用户输入的字符编码,然后再把编码结果翻译成原字符。
但在进行这些操作之前必须做一项工作,就是创建Huffman树。
哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。
所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。
树的带权路径长度记为WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln),N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。
可以证明哈夫曼树的WPL是最小的。
哈夫曼在上世纪五十年代初就提出这种编码时,根据字符出现的概率来构造平均长度最短的编码。
它是一种变长的编码。
在编码中,若各码字长度严格按照码字所对应符号出现概率的大小的逆序排列,则编码的平均长度是最小的。
4
3.2程序框图及流程图
3.3方法及原理
3.3.1 创建Huffman树
本文创建Huffman树是在顺序链表的基础上进行的,建树原理如下:
1、根据给定的n个权值{w1,w2,w3,……,wn},构造具有n棵二叉树的森林F={T1,T2,T3,……,Tn},其中每棵二叉树Ti只有一个带权值wi的根结点,其左右子树均为空。
2、重复以下步骤,直到F中仅剩下一棵树为止:
(1)在F中选取两棵根结点的权值最小的二叉树,作为左右子树构造一棵新的二叉树。
使新的二叉树的根结点的权值为其左右子树上根结点的权值之和。
(2)在F中删去这两棵二叉树,把新的二叉树加入F。
最后得到的就是Huffman树。
3.3.2编码
编码操作需在建立好Huffman树的基础上进行。
二叉树的叶子结点标记字符,由根结点沿着二叉树路径下行,左分支标记为0,右分支标记为1,则每条从根结点到叶子结点的路径唯一表示了该叶结点的二进制编码。
编码的时候,我们采用从叶子结点向上回溯的方法编码,如果当前结点是其父结点的左孩子,则编码为0,如果是右孩子,则编码为1,如此回溯,直到父结点为空时,该字符的编码就结束了,对应编码结构中的编码数组就是该字符的编码。
如此操作,直到所有叶子结点都扫描一遍为止,即编码结束。
3.4主要的数据结构
3.4.1Huffman结点结构
Huffman结点结构是本程序的基本结构,所有操作都在此上进行。
其中包括存储字符的元素data,字符的权值weight,以及左右孩子指针和父指针。
typedefstruct
{
charch;//结点值
intweight;//权值
intparent;//父结点指针
intlchild;//左孩子结点指针
intrchild;//右孩子结点指针
}huffnode;
详细设计
4.1创建Huffman树
4.1.1功能描述
Huffman树是整个程序的核心部分,是编码译码操作的前提。
哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。
根据字符出现的概率来构造平均长度最短的编码。
它是一种变长的编码。
在编码中,若各码字长度严格按照码字所对应符号出现概率的大小的逆序排列,则编码的平均长度是最小的。
4.1.2算法原理
首先根据用户输入创建n棵子树的森林,然后对所有子树扫描,找出权值最小的两个子树,把它们合并成一棵新的子树,同时把它们的权值之和作为新树的权值。
把这两棵子树删掉,再把新树加如到森林中,然后再扫描出权值最小的两棵子树,接着进行同样的操作,直到只剩下一棵树即为Huffman树。
4.1.3算法流程
7
4.2编码
4.2.1功能描述
编码的功能就是把字符转换成二进制数来存储
4.2.2算法原理
编码的时候,我们采用从叶子结点向上回溯的方法编码,如果当前结点是其父结点的左孩子,则编码为0,如果是右孩子,则编码为1,如此回溯,直到父结点为空时,该字符的编码就结束了,对应编码结构中的编码数组就是该字符的编码。
如此操作,直到所有叶子结点都扫描一遍为止,即编码结束。
4.2.3算法流程
4.4源程序
#include
#include
#include
#include
#include
typedefstruct{//哈夫曼树的结构体
charch;
intweight;//权值
intparent,lchild,rchild;
}htnode,*hfmtree;
typedefchar**hfmcode;
voidSelect(hfmtree&HT,inta,int*p1,int*p2)//Select函数,选出HT树到a为止,权值最小且parent为0的2个节点
{
inti,j,x,y;
for(j=1;j<=a;++j){
if(HT[j].parent==0){
x=j;
break;
}
}
for(i=j+1;i<=a;++i){
if(HT[i].weightx=i;//选出最小的节点
}
}
for(j=1;j<=a;++j){
if(HT[j].parent==0&&x!
=j)
{
y=j;
break;
}
}
for(i=j+1;i<=a;++i)
{
if(HT[i].weight=i)
{
y=i;//选出次小的节点
}
}
if(x>y){
*p1=y;
*p2=x;
}
else
{
*p1=x;
*p2=y;
}
}
voidhfmcoding(hfmtree&HT,hfmcode&HC,intn)//构建哈夫曼树HT,并求出n个字符的哈夫曼编码HC
{
inti,start,c,f,m,w;
intp1,p2;
char*cd,z;
if(n<=1){
return;
}
m=2*n-1;
HT=(hfmtree)malloc((m+1)*sizeof(htnode));
for(i=1;i<=n;++i)//初始化n个叶子结点
{
printf("请输入第%d字符信息和权值:
",i);
scanf("%c%d",&z,&w);
while(getchar()!
='\n')
{
continue;
}
HT[i].ch=z;
HT[i].weight=w;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
for(;i<=m;++i)//初始化其余的结点
{
HT[i].ch='0';
HT[i].weight=0;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
for(i=n+1;i<=m;++i)//建立哈夫曼树
{
Select(HT,i-1,&p1,&p2);
HT[p1].parent=i;HT[p2].parent=i;
HT[i].lchild=p1;HT[i].rchild=p2;
HT[i].weight=HT[p1].weight+HT[p2].weight;
}
HC=(hfmcode)malloc((n+1)*sizeof(char