Huffman树及Huffman编码的算法实现(必做,验证性实验)
实验目的
熟悉掌握Huffman树的构造方法及Huffman编码的应用,了解Huffman树在通信、编码领域的应用过程。
实验内容
(1)输入一段100—200字的英文短文,存入一文件a中。
(2)写函数统计短文出现的字母个数n及每个字母的出现次数
(3)写函数以字母出现次数作权值,建Haffman树(n个叶子),给出每个字母的Haffman编码。
(4)用每个字母编码对原短文进行编码,码文存入文件b中。
(5)用Haffman树对文件b中码文进行译码,结果存入文件c中,比较a,c是否一致,以检验编码、译码的正确性。
数据结构定义
算法中使用的数据结构是链表,用链表来创建哈夫曼树,哈夫曼树中的每一个节点中出现的元素有每一个节点的权值,以及该节点的双亲,左孩子以及右孩子。然后又创建了一个node类型的节点用来存储字符以及字符的信息。
同时,为了方便大小比较以及排序,我定了一个vector容器,方便之后的操作。
#include "constant.h"
#include <stdio.h>
#include <string.h>
typedef char **huffmancode;
#define maxsize 300
#define INF 1e5
typedef struct
{
/* data */
int weight;
int parent, lchild, rchild;
} HTNode, *huffmantree;
typedef struct node
{
char ch;
int data;
} node;
void createhuffmantree(huffmantree &HT, huffmancode &HC, int *w, int n); //创建哈夫满树
void huffmancoding(huffmantree &HT, huffmancode &HC, int *w, int n); //对哈夫曼树进行编码,找到其对应的哈夫曼编码
void select(huffmantree HT, int length, int &s1, int &s2); //挑选出权值最小的两个数
void read(int weight[maxsize], int &length); //将数组中的权值读入权值数组中
void count();
void compare();
void translate();
算法思想及算法设计
void select(huffmantree HT, int length, int &s1, int &s2)
{
//s1存储权制最小的节点,s2存储第二小的节点
if (length <= 2)
exit(0);
int s1a = 9999999, s2a = 9999999; //默认存储前两个个数
for (int k = 1; k <= length; k++)
{
if (HT[k].parent == 0) //不断进行优化,找出最好的,最方便的优化结果,优化方案
{
if (HT[k].weight < s1a) //先找到最小的节点
{
s1a = HT[k].weight;
s1 = k;
}
}
}
for (int k = 1; k <= length; k++)
{
if (HT[k].parent == 0) //不断进行优化,找出最好的,最方便的优化结果,优化方案
{
if (HT[k].weight < s2a && HT[k].weight != s1a)
{
s2a = HT[k].weight;
s2 = k;
}
}
}
}
void pr(node a)
{
cout << a.ch << " " << a.data << endl;
}
void print(pair<char, int> v)
{
//cout<<v.first<<" "<<v.second<<endl;
node temp = {v.first, v.second};
v1.push_back(temp);
}
void read(int weight[maxsize], int &length, char weightch[maxsize])
{
weight[maxsize] = {0};
FILE *fp;
multimap<char, int> wgh;
fp = fopen("shiyanshuju.txt", "r");
char ch = fgetc(fp);
while (ch != EOF)
{
if (wgh.find(ch) == wgh.end())
{
wgh.insert(make_pair(ch, 1));
}
else
{
wgh.find(ch)->second++;
}
ch = fgetc(fp);
}
fclose(fp);
for_each(wgh.begin(), wgh.end(), print);
vector<node>::iterator its = v1.begin();
for (int i = 0; i < v1.size(); i++)
{
for (int j = i + 1; j < v1.size(); j++)
{
if (v1[i].data <= v1[j].data)
{
node temp = v1[j];
v1[j] = v1[i];
v1[i] = temp;
}
}
}
//for_each(v1.begin(), v1.end(), pr);
int i = 1;
length = wgh.size();
its = v1.begin();
while (its != v1.end())
{
weight[i] = its->data;
weightch[i] = its->ch;
its++;
i++; //别乱加
}
return;
}
void createhuffmantree(huffmantree &HT, huffmancode &HC, int *w, int n)
{
if (n <= 1) //创建哈夫曼树,然后后面是秋哈夫曼编码
{
return;
}
int m = 2 * n - 1;
huffmantree p;
int i = 0;
int s1, s2;
w++;
HT = (huffmantree)malloc((m + 1) * sizeof(HTNode));
for (p = HT, i = 1; i <= n; i++, ++p, ++w)
{
*p = {*w, 0, 0, 0};
}
for (; i <= m; i++, ++p)
{
*p = {0, 0, 0, 0};
}
for (i = n + 1; i <= m; i++)
{
select(HT, i - 1, s1, s2);
HT[s1].parent = i;
HT[s2].parent = i;
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
}
void huffmancoding(huffmantree &HT, huffmancode &HC, int *w, int n)
{ //进行哈夫曼编码
int i = 0;
//从叶子到根逆向求哈夫曼编码
HC = (huffmancode)malloc((n + 1) * sizeof(char *));
char *cd = (char *)malloc(n * sizeof(char));
cd[n - 1] = '\0';
int start;
for (i = 1; i <= n; i++)
{
start = n - 1;
for (int c = i, f = HT[i].parent; f != 0; c = f, f = HT[f].parent)
{
if (HT[f].lchild == c)
cd[--start] = '0';
else
cd[--start] = '1';
}
HC[i] = (char *)malloc((n - start) * sizeof(char));
strcpy(HC[i], &cd[start]);
}
free(cd);
}
void bianma(huffmancode &HC, char weightch[maxsize])
{
FILE *fp1, *fp2;
fp1 = fopen("bianma.txt", "w"); //写文件
fp2 = fopen("shiyanshuju.txt", "r"); //读文件
char ch = fgetc(fp2);
while (ch != EOF)
{
int i = 1;
while (weightch[i] != ch && weightch[i] != '\n')
{
i++;
}
fprintf(fp1, "%s", HC[i]);
ch = fgetc(fp2);
}
fclose(fp1);
fclose(fp2);
}
void jiema(huffmantree HT, char weightch[maxsize], int m)
{
FILE *fp;
char ch = '\n';
fp = fopen("bianma.txt", "r");
loop:
int i = m;
while (HT[i].lchild != 0 && HT[i].rchild != 0)
{
ch = fgetc(fp);
if (ch == EOF)
{
fclose(fp);
return;
}
if (ch == '1') //注意这里的左右对应关系
i = HT[i].rchild;
else
i = HT[i].lchild;
}
printf("%c", weightch[i]);
goto loop;
}
实验代码
部分算法已在上面给出:
下面是为给出的连接部分:
#ifndef constant_h
#define constant_h
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status;
#endif /* constant_h */
#include <stdio.h>
#include <iostream>
#include "huffmancode.hpp"
#include "constant.h"
#include <stdlib.h>
#include <string.h>
#include <string>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;
void huffmancoding(huffmantree &HT, huffmancode &HC, int *w, int n);
void select(huffmantree HT, int length, int &s1, int &s2);
void read(int weight[maxsize], int &length, char weightch[maxsize]);
void huffmancoding(huffmantree &HT, huffmancode &HC, int *w, int n);
void createhuffmantree(huffmantree &HT, huffmancode &HC, int *w, int n);
void huffmancoding(huffmantree &HT, huffmancode &HC, int *w, int n);
void bianma(huffmancode &HC, char weightch[maxsize]);
void jiema(huffmantree HT, char weightch[maxsize], int m);
vector<node> v1;
int main()
{
int length = 0;
huffmantree HT;
huffmancode HC;
int weight[maxsize] = {0};
char weightch[maxsize] = {'\n'};
read(weight, length, weightch);
createhuffmantree(HT, HC, weight, length);
huffmancoding(HT, HC, weight, length);
bianma(HC, weightch);
int m = length * 2 - 1;
printf("把哈夫曼编码解码可得:\n");
jiema(HT, weightch, m);
}
算法测试结果
以下为测试文档内容:
分析与总结
(1)算法复杂度分析及优、缺点分析
哈夫曼编码的创建过程中的时间复杂度为:O(n),哈夫曼过程中涉及的排序操作是采用快排序,时间复杂度为O(nlogn),哈夫曼编码在创建过程中就采用最优方法,将权值最大的节点放在靠近根节点的位置,使得编码之后的哈夫曼编码变得简单,而且没有二义性,非常简单
(2)实验总结
在编写select函数时 ,不能迅速找到方法,在认真思考以及修改之后找到了寻找出最大的两个节点。
刚开始在读取实验数据文档的时候不能迅速的对应每个字母,每一个字母都出现了乱码,我刚开始找了好久,也没找到,最终看见有一个地方读取键盘输入信息的时候把那回车键也输入进去,导致出现了很多错误。而且对应排序之后的数组,不能迅速找到对应,经过几番周折,最终利用vector容器找到解决办法。
在最后解码的时候,把左右子树对应的0,1代码搞反,导致整片文档翻译出的东西一个也对不上,导致出现很多乱码。后来在慢慢找错误的时候找到错误的地方。