数据结构实验报告1.实验要求本实验为可选实验,用于提高同学们使用数据结构解决实际问题的能力。
通过选择下面3个题目之一进行实现,掌握如下内容:栈和递归地深度应用了解Huffman的思想和算法实现矩阵和相关算法在BMP图像中的应用进一步提高编程能力利用二叉树结构实现哈夫曼编/解码器。
基本要求:1、初始化(Init):能够对输入的任意长度的字符串s进行统计,统计每个字符的频度,并建立哈夫曼树2、建立编码表(CreateTable):利用已经建好的哈夫曼树进行编码,并将每个字符的编码输出。
3、编码(Encoding):根据编码表对输入的字符串进行编码,并将编码后的字符串输出。
4、译码(Decoding):利用已经建好的哈夫曼树对编码后的字符串进行译码,并输出译码结果。
5、打印(Print):以直观的方式打印哈夫曼树(选作)6、计算输入的字符串编码前和编码后的长度,并进行分析,讨论赫夫曼编码的压缩效果。
7、可采用二进制编码方式(选作)测试数据:I love data Structure, I love Computer.I will try my best to study data Structure.提示:1、用户界面可以设计为“菜单”方式:能够进行交互。
2、根据输入的字符串中每个字符出现的次数统计频度,对没有出现的字符一律不用编码。
2. 程序分析2.1存储结构:(1)三叉树:class Huffman{private:HNode*HTree;//哈夫曼树结点HCode*HCodeTable;//哈夫曼编码表char b[1000];//记录所有输入内容被编码后的结果char c[127];char letter[1000];//输入内容的保存void SelectMin(int &x,int &y,int k);//求最小权重的字符node*count;//计算各个字符出现次数int n;//输入字符的种类(个数)int l;public:Huffman();void CreateHTree();//创建哈夫曼树void CreateCodeTable();//创建哈夫曼编码表void Encode();//编码void Decode();//解码};结点结构为如下所示:三叉树的节点结构:struct HNode//哈夫曼树结点的结构体{ int weight;//结点权值int parent;//双亲指针int lchild;//左孩子指针int rchild;//右孩子指针char data;//字符};示意图为:int weight int parent int lchild int rchild chardata编码表节点结构:struct HCode//编码表结构体{char data;//字符char code[100];//编码内容};示意图为:char data char code[100]基本结构体记录字符和出现次数:struct node{int num;char data;};示意图为:int num char data2.关键算法分析(1).初始化:伪代码:1.输入需要编译的文本内容2.将输入的内容保存到动态建立的node型数组count中3.统计出现的字符种类的数目,并且保存到private型变量nHuffman::Huffman()//将输入数据保存到Huffman类中{l=0;n=0;count=new node[127];cout<<"请输入需要编译压缩的内容"<<endl;cin.getline(letter,200,'\n');for(int j=0;j<127;j++) //一个号码代表一种字符{count[j].num=0;}while(letter[l]!='\0')//在结束之前,每输入一个字符,则对应字符的数目则自增1 {++count[letter[l]].num;count[letter[l]].data=letter[l];++l;}for(int k=0;k<127;k++) {if(count[k].num>0){n++;}//在某个字符出现此书num不为0时,n自增1,最终n为出现的字符种类数目}其时间复杂度为O(n)(2).创建哈夫曼树(void HuffmanTree::CreateCodeTable(Node *p))算法伪代码:1.创建一个长度为2*n-1的三叉链表2.将存储字符及其权值的链表中的字符逐个写入三叉链表的前n个结点的data域,并将对应结点的孩子域和双亲域赋为空3.从三叉链表的第n个结点开始,3.1从存储字符及其权值的链表中取出两个权值最小的结点x,y,记录其下标x,y。
3.2将下标为x和y的哈夫曼树的结点的双亲设置为第i个结点3.3将下标为x的结点设置为i结点的左孩子,将下标为y的结点设置为i结点的右孩子,i结点的权值为x结点的权值加上y结点的权值,i结点的双亲设置为空4.根据哈夫曼树创建编码表源代码为:void Huffman::CreateHTree(){l=0;HTree=new HNode[2*n-1];//建立含有n种字符的哈夫曼树只需要2*n-1个结点即可for(int i=0;i<n;i++){while (count[l].num==0)//如果count内的权重为0,即该字符没有出现,则跳过,i自增继续寻找出现过的字符{l++;}HTree[i].weight=count[l].num;//将count里统计的次数传入哈夫曼树的节点中,作为字符权重HTree[i].lchild=-1;HTree[i].rchild=-1;HTree[i].parent=-1;//将左右孩子结点和父节点都置空HTree[i].data=count[l].data;//将count内的有效字符传入哈夫曼树的结点l++;}int x=-1,y=-1;for(int i=n;i<2*n-1;i++)//开始建立哈夫曼树{SelectMin(x,y,i);//挑选三者中的权重较小的两个HTree[x].parent=HTree[y].parent=i;//令较小的x、y为孩子节点,该两个结点的父节点是iHTree[i].weight=HTree[x].weight+HTree[y].weight;//i结点字符的权重赋为是左右孩子字符权重之和HTree[i].lchild=x;//左孩子为xHTree[i].rchild=y;//右孩子为yHTree[i].parent=-1;//父节点置空x=-1;y=-1;//将x、y重新赋值为零,进行下一次比较建树}其时间复杂度为:O(n)(3).创建编码表算法伪代码:1.初始化编码表2.初始化一个指针,从链表的头结点开始,遍历整个链表2.1 将链表中指针当前所指的结点包含的字符写入编码表中2.2 得到该结点对应的哈夫曼树的叶子结点及其双亲2.3 如果哈夫曼树只有一个叶子结点,将其字符对应编码设置为02.4 如果不止一个叶子结点,从当前叶子结点开始判断2.4.1 如果当前叶子结点是其双亲的左孩子,则其对应的编码为0,否则为12.4.2 child指针指向叶子结点的双亲,parent指针指向child指针的双亲,重复2.4.1的操作2.5 将已完成的编码倒序2.6 取得链表中的下一个字符3.输出编码表源代码为:void Huffman::CreateCodeTable()//建立哈夫曼编码表{HCodeTable=new HCode[n];//建立动态编码表for(int i=0;i<n;i++){HCodeTable[i].data=HTree[i].data;int child=i;int parent=HTree[i].parent;int k=0;while(parent!=-1){if(child==HTree[parent].lchild)//判断该节点是父节点的左孩子或右孩子,左孩子则编码为0,右孩子则编码为1HCodeTable[i].code[k]='0';elseHCodeTable[i].code[k]='1';k++;child=parent;//将该节点的父节点作为新的孩子节点,继续进行编码输出parent=HTree[child].parent;}HCodeTable[i].code[k]='\0';//code数组以\0结尾Reverse(HCodeTable[i].code);//由于是从下到上输出,顺序是相反的,所以还需要逆置才能输出字符的编码值}cout<<endl<<endl<<"每个字符的编码为:"<<endl;for(int i=0;i<n;i++){cout<<HCodeTable[i].data<<": "<<HCodeTable[i].code<<endl;//逐个输出对应的字符和其编码}}}其时间复杂度为:O(n)(4)选择两个最小权值的函数void Huffman::SelectMin(int &x,int &y,int k) 算法伪代码:1.从下标为i=0的开始遍历。
前两次将x,y赋值为序号最小的两个结点的地址序号。
2.开始进行比较:进行如下分类对于任何不存在父节点的结点:若x权值<=y权值(1)且i权值>=y权值,则无疑i权值最大,为输出x、y为权值较小的两个故而x,y值不便;(2)其余情况皆为x、i的权值是较小的两个,令y赋值为i,则保证x、y权值是最小的两个。
若y权值<=x权值(1)且i权值>=x权值,则i权值是最大,x、y不变。
(2)其余情况皆为i、y权值最小,令x赋值为i,保证x、y序号结点的权值最小3.完成如上循环,直至i=k则推出循环,第k个结点在树的位置已经确定源代码为:void Huffman::SelectMin(int &x,int &y,int k)//选出权值较小的两个字符结点{int i=0;while(i<k){while(i<k&&HTree[i].parent==-1)//若结点不具有父结点且满足i<k则进行如下循环,建立新子树{if(x==-1)x=i;else if(y==-1)y=i;elseif(HTree[x].weight<=HTree[y].weight){if(HTree[y].weight<=HTree[i].weight){y=y;x=x;}elsey=i;}elseif(HTree[x].weight>HTree[y].weight){if(HTree[i].weight>=HTree[x].weight){x=x;y=y;}elsex=i;}i++;}i++;}}其时间复杂度为O(n)(5). 将字符串倒序的函数(void HuffmanTree::Reverse(char *pch))算法伪代码:1.得到字符串的长度2.初始化两个记录下标的变量,一个为字符串开头字符所在的下标i,另一个为字符串结尾字符所在的下标j3.将下标为i和j的字符交换4.i++,j - -源代码:void Reverse(char a[]){char b[100];int i=0,j=0;while(a[i]!='\0'){b[i]=a[i];i++;}b[i]='\0';i--;while(i>=0){a[j]=b[i];i--;j++;}a[j]='\0';}其时间复杂度为O(n)(6).编码函数(void Huffman::Encode(a[]))算法伪代码:1. 从开头的字符开始,逐一对a中的字符进行编码2. 在编码表中查找与当前字符对应的字符3.如果找到了与当前字符对应的编码表中的字符,将其编码追加到解码串的末尾。