哈夫曼编码实现
需实现的功能
//实现的功能
//1.输入一行字符串 找出各个字符出现的次数 及其对应的字符,将其保存在二叉树中,组成森林//或者组成二叉树
//2.从森林中挑选权值最小的两颗的合并,且这两颗作为新树的左右树,(左小右大),且新树的权值为此两颗树之和。
//3.从森林中删除那两颗树,并将新树加入
//4.重复2 3两步 直至森林中树为1。
//5.加编码
//6.返回权值
//7.解码
这里实现了 看代码咯
输入不同字符串并统计
功能:输入任何字符 统计每个不同字符的个数
cout << "Please enter the string to be encoded !" << endl;
fflush(stdin);
//string s; //字符串 区别在于不能够输入空格的ASCII码
//cin>> s;
char s[MAX];//能输入空格 不过要提前定义一个足够大数组
cin.get(s,MAX);
//概率初始化
//int *probabiblity = new int[128];
for (int i = 0; i < 128; i++)
{
probabiblity[i] = 0;
}
/*for (int i = 0; i < s.size(); i++)//检验字符串
{
cout << s[i] << endl;
}*/
//统计0-128个字符中出现的次数 按照ASCII码排序
for (int j = 0; s[j] != '\0'; j++)
{
//cout << s[j] << endl;
probabiblity[s[j]]++;//这里我的理解是pro[] 里面是int型 放过字符 强制转换成对应的int类型
}
//统计不同字符数 sum计数
for (int k = 0; k < 128; k++)
{
if (probabiblity[k] != 0)
{
sum++;//不同字符数目
//cout <<k<<'\t'<< probabiblity[k] <<(char)k<< endl;
}
else
continue;
}
return 0;
}
生成哈夫曼树
一开始输入字符也在生成哈夫曼树里的
但是为了更方便 将其拆开了
首先将保存字符数量信息的数组改成从零开始
!!!!这里我的操作是按ASCII码排序 而不是按出现的次数排序
Huffman CreatTree(Huffman *Tree,int *probabiblity,int &sum)
{
/*
int sum = 0;
string s;
cout << "Please enter the string to be encoded !" << endl;
fflush(stdin);
cin >> s;
//概率初始化
int *probabiblity = new int[128];
for (int i = 0; i < 128; i++)
{
probabiblity[i] = 0;
}
/*for (int i = 0; i < s.size(); i++)//检验字符串
{
cout << s[i] << endl;
}*/
/*
//统计0-128个字符中出现的次数 按照ASCII码排序
for (int j = 0; s[j] !='\0'; j++)
{
//cout << s[j] << endl;
probabiblity[s[j]]++;
}
//统计不同字符数 sum计数
for (int k = 0; k < 128; k++)
{
if (probabiblity[k] != 0)
{
sum++;//不同字符数目
//cout <<k<<'\t'<< probabiblity[k] <<(char)k<< endl;
}
else
continue;
}
//cout << sum << endl;
*/
//sum=Enterstring(probabiblity);
/*
int sum=0;
int *probabiblity = new int[128];
Enterstring(probabiblity,sum);*/
//HuffmanCode Tree[128];//但是这样好像比较占内存 后续改改
//Huffman *Tree = (Huffman*)malloc((2*sum-1)* sizeof(HTNODE));
int t,x=0;
//用数组保存树的结构信息 将数组序号改成从0依次增加。
for (int k = 0; k < 128; k++)
{
if (probabiblity[k] != 0) //k是0-128之间对应着ASCII码。
{
t = k; //k的值用t保存
Tree[x] = new HuffmanTree; //将x取代数组序号k
Tree[x]->lchild = -1; //就是因为一开始将孩子信息定义为整数型 导致后面没应用递归
Tree[x]->rchild = -1; //如果孩子结点是树的结构体指针 那么用递归会方便很多
Tree[x]->parent = -1;
Tree[x]->symbol = (char)t; //也是将int型强制转换为char型 //元素 合成的结点 就没有放元素
Tree[x]->weigth = probabiblity[t]; //权重 即次数
x++;
//上面的树 如果不加数组的话 每次循环都会有变化,应该每次都将这些树加入到一个序列当中!!!或者组成森林 但是还是没有排序
//先尝试编一下森林的
}
else
continue;
}
free(probabiblity);//释放空间
//输出二叉树看看
cout << "输出一群二叉树" << endl; //没问题
for (int k = 0; k < sum; k++)
{
cout << k << '\t' << Tree[k]->weigth <<'\t'<< Tree[k]->symbol << endl;
}
//完成了单个二叉树的构造
int min1, min2; //min 为左树数组序号 max为右树数组序号
//sum 为需要编码的叶子数目
for (int j = sum; j < 2 * sum - 1; j++)
{
SortTree(Tree,j, min1, min2);//这里需要找到一个能够将数组中找出权值最小的两颗树,并删除此两颗树,然后生成一个权值为二者之和的新树。整体-2+1。当树的数目为1是停止。
Tree[min1]->parent = j;
Tree[min2]->parent = j;
Tree[j] = new HuffmanTree;//动态
Tree[j]->symbol = NULL;
/*Tree[j]->lchild = Tree[min1];
Tree[j]->rchild = Tree[min2];*/
Tree[j]->lchild = min1;
Tree[j]->rchild = min2;
Tree[j]->weigth = Tree[min1]->weigth + Tree[min2]->weigth;
Tree[j]->parent = -1;//这一步开始忘记了 导致不能使得新结点进入排序
}
//需要编码的多个信息都已经保存到了对应不同的二叉树中
//下面要做的就是对二叉树进行排序 权值分配。AADATARAEFRTAAFTER
cout << "Output Huffman tree" << endl; //没问题
cout << "character" << '\t' << "subscript" <<'\t' << "weight" << endl;
for (int k = 0; k < 2*sum-1; k++)
{
cout<<Tree[k]->symbol << '\t'<<'\t' << k <<'\t'<< '\t' << Tree[k]->weigth << endl;
}
return *Tree;
}
给哈夫曼树进行编码
这里搜索的还不太熟悉 然后自己想又一时想不出了 纠结了很久唉 提醒一下自己!!!
后面加上编码的时候一时没有加上,后来看了其他的代码 还是这方面练习的少啊
如上图所示,每个结点都有数组下标,而且需要编码的字符都在最前面
所以可以先从第一个点开始找
比如先找到C结点,其数组下标为0
用current记录此时下标为0;
然后我们可以找到C的双亲结点(权值为7)用father记录其数组下标(11)
再判断current是father的左孩子还是右孩子
左孩子记录为0;右孩子记录为1
如何记录呢 用新的数组,(需要编码的长度最长不超过 不同字符数-1)
又因为是从尾部开始的 所以code[--start]
循环寻找每一个不同的字符位置
code是一个中间保存编码的值
start每次都不同 且编码在后面
所以用strcmp(H_code,code+start)复制过去
//从根结点到叶子结点遍历哈弗曼树 生成哈夫曼编码
Status HuffmanCode1(Huffman *Tree, Huffmancode &H_Code,int &sum)
{
H_Code = (Huffmancode)malloc(sum * sizeof(char *));//记录编码
char *cd = (char*)malloc(sum * sizeof(char));//
//int temp, count;
cd[sum - 1] = '\0';//哈夫曼编码最长为需要编码不同字符-1;
int i;
for (i = 0; i < sum; i++)
{
int current = i;
int father = Tree[i]->parent;
int start = sum - 1;
while (father != -1)
{
if (Tree[father]->lchild == current)
{
cd[--start] = '0';
}
else
{
cd[--start] = '1';
}
current = father;
father = Tree[father]->parent;
}
H_Code[i] = (char *)malloc((sum - start) * sizeof(char));
strcpy(H_Code[i], cd + start);
}
for (int i = 0; i < sum; ++i) {
cout << H_Code[i] << endl;;
}
free(cd);
return 0;
}
在用strcmp的时候遇到错误解决办法——在最前面加上这个
#pragma warning( disable : 4996)
//从根结点到叶子结点遍历哈弗曼树 生成哈夫曼编码
Status HuffmanCode2(Huffman *Tree,Huffmancode &H_Code,int &sum)
{
cout <<"Traversing Huffman tree from leaf node to root node" << endl;
H_Code = (Huffmancode)malloc(sum * sizeof(char *)); //保持编码信息
char *cd = (char*)malloc(sum * sizeof(char)); //生成编码的过渡信息
int *a = (int *)malloc((2 * sum - 2 + 1) * sizeof(int)); //保存权值信息
int cur = 2 * sum - 2;
int cd_len = 0;
for (int i = 0; i < cur + 1; i++) //以权值作为信息
{ //权值信息都为0 然后用数组a保存原权值
a[i] = Tree[i]->weigth;
Tree[i]->weigth = 0;
}
//这里要好好看看
//编码过程
while (cur != -1)
{
if (Tree[cur]->weigth == 0)//权值为0时说明没有进行遍历
{
Tree[cur]->weigth = 1;//权值为1 说明进行了左遍历
if (Tree[cur]->lchild != -1)//NULL:空指针, 在C++中值为0,在C中定义为(void *)0.
{
cd[cd_len++] = '0';
cur = Tree[cur]->lchild;
}
else
{
cd[cd_len] = '\0';
H_Code[cur]= (char *)malloc((cd_len + 1) * sizeof(char));
strcpy(H_Code[cur],cd);
}
}
else if (Tree[cur]->weigth == 1)
{
Tree[cur]->weigth = 2;//权值为2进行了右遍历
if (Tree[cur]->rchild != -1)
{
cd[cd_len++] = '1';
cur = Tree[cur]->rchild;
}
}
else
{
//Tree[cur]->weigth = 0;
cur = Tree[cur]->parent;//结束标志
--cd_len;
}
}
for (int i = 0; i < 2 * sum - 2 + 1; i++)
{
Tree[i]->weigth= a[i];//还原权值
}
for (int i = 0; i < sum; ++i) {//输出编码
cout << H_Code[i] << endl;;
}
free(cd);
free(a);
return 0;
}
完整编程
#pragma warning( disable : 4996)
#include<iostream>
#include<string>
#include <cstring>
#include<malloc.h>
using namespace std;
#define MAX 100
typedef int Status;
typedef char element;
//实现的功能
//1.输入一行字符串 找出各个字符出现的次数 及其对应的字符,将其保存在二叉树中,组成森林//或者组成二叉树
//2.从森林中挑选权值最小的两颗的合并,且这两颗作为新树的左右树,(左小右大),且新树的权值为此两颗树之和。
//3.从森林中删除那两颗树,并将新树加入
//4.重复2 3两步 直至森林中树为1。
//5.加编码
//6.返回权值
//7.解码
typedef struct HuffmanTree
{
int weigth;
int parent;
char symbol;
//struct HuffmanTree *lchild, *rchild;
int lchild, rchild;
}HTNODE,*Huffman;
//typedef char **HCode;
typedef char **Huffmancode;
Status Enterstring(int *probabiblity, int &sum);
Huffman CreatTree(Huffman *Tree, int *probabiblity, int &sum);
void SortTree(Huffman *Tree, int j, int &min1, int &min2);
Status find_D_min(Huffman *tree, int j);
Status HuffmanCode1(Huffman *Tree, Huffmancode &H_Code, int &sum);
Status HuffmanCode2(Huffman Tree, Huffmancode &H_Code, int &sum);
Status WPL1(Huffman *Tree, int &sum);
Status WPL1(Huffman *Tree, int &sum);
//因为需要知道需要编码数 n 所以需要把字符串输入这一个函数单独写出来
Status Enterstring(int *probabiblity,int &sum)
{
cout << "Please enter the string to be encoded !" << endl;
fflush(stdin);
//string s; //字符串 区别在于不能够输入空格的ASCII码
//cin>> s;
char s[MAX];//能输入空格 不过要提前定义一个足够大数组
cin.get(s,MAX);
//概率初始化
//int *probabiblity = new int[128];
for (int i = 0; i < 128; i++)
{
probabiblity[i] = 0;
}
/*for (int i = 0; i < s.size(); i++)//检验字符串
{
cout << s[i] << endl;
}*/
//统计0-128个字符中出现的次数 按照ASCII码排序
for (int j = 0; s[j] != '\0'; j++)
{
//cout << s[j] << endl;
probabiblity[s[j]]++;
}
//统计不同字符数 sum计数
for (int k = 0; k < 128; k++)
{
if (probabiblity[k] != 0)
{
sum++;//不同字符数目
//cout <<k<<'\t'<< probabiblity[k] <<(char)k<< endl;
}
else
continue;
}
return 0;
}
Huffman CreatTree(Huffman *Tree,int *probabiblity,int &sum)
{
/*
int sum = 0;
string s;
cout << "Please enter the string to be encoded !" << endl;
fflush(stdin);
cin >> s;
//概率初始化
int *probabiblity = new int[128];
for (int i = 0; i < 128; i++)
{
probabiblity[i] = 0;
}
/*for (int i = 0; i < s.size(); i++)//检验字符串
{
cout << s[i] << endl;
}*/
/*
//统计0-128个字符中出现的次数 按照ASCII码排序
for (int j = 0; s[j] !='\0'; j++)
{
//cout << s[j] << endl;
probabiblity[s[j]]++;
}
//统计不同字符数 sum计数
for (int k = 0; k < 128; k++)
{
if (probabiblity[k] != 0)
{
sum++;//不同字符数目
//cout <<k<<'\t'<< probabiblity[k] <<(char)k<< endl;
}
else
continue;
}
//cout << sum << endl;
*/
//sum=Enterstring(probabiblity);
/*
int sum=0;
int *probabiblity = new int[128];
Enterstring(probabiblity,sum);*/
//HuffmanCode Tree[128];//但是这样好像比较占内存 后续改改
//Huffman *Tree = (Huffman*)malloc((2*sum-1)* sizeof(HTNODE));
int t,x=0;
//用数组保存树的结构信息 将数组序号改成从0依次增加。
for (int k = 0; k < 128; k++)
{
if (probabiblity[k] != 0) //k是0-128之间对应着ASCII码。
{
t = k; //k的值用t保存
Tree[x] = new HuffmanTree; //将x取代数组序号k
Tree[x]->lchild = -1;
Tree[x]->rchild = -1;
Tree[x]->parent = -1;
Tree[x]->symbol = (char)t; //元素
Tree[x]->weigth = probabiblity[t]; //权重 即次数
x++;
//上面的树 如果不加数组的话 每次循环都会有变化,应该每次都将这些树加入到一个序列当中!!!或者组成森林 但是还是没有排序
//先尝试编一下森林的
}
else
continue;
}
free(probabiblity);//释放空间
//输出二叉树看看
cout << "输出一群二叉树" << endl; //没问题
for (int k = 0; k < sum; k++)
{
cout << k << '\t' << Tree[k]->weigth <<'\t'<< Tree[k]->symbol << endl;
}
//完成了单个二叉树的构造
int min1, min2; //min 为左树数组序号 max为右树数组序号
//sum 为需要编码的叶子数目
for (int j = sum; j < 2 * sum - 1; j++)
{
SortTree(Tree,j, min1, min2);//这里需要找到一个能够将数组中找出权值最小的两颗树,并删除此两颗树,然后生成一个权值为二者之和的新树。整体-2+1。当树的数目为1是停止。
Tree[min1]->parent = j;
Tree[min2]->parent = j;
Tree[j] = new HuffmanTree;
Tree[j]->symbol = NULL;
/*Tree[j]->lchild = Tree[min1];
Tree[j]->rchild = Tree[min2];*/
Tree[j]->lchild = min1;
Tree[j]->rchild = min2;
Tree[j]->weigth = Tree[min1]->weigth + Tree[min2]->weigth;
Tree[j]->parent = -1;
}
//需要编码的多个信息都已经保存到了对应不同的二叉树中
//下面要做的就是对二叉树进行排序 权值分配。AADATARAEFRTAAFTER
cout << "Output Huffman tree" << endl; //没问题
cout << "character" << '\t' << "subscript" <<'\t' << "weight" <<'\t'<<"parent"<< '\t' <<"lchild"<< '\t' << "rchild"<< endl;
for (int k = 0; k < 2*sum-1; k++)
{
cout<<Tree[k]->symbol << '\t'<<'\t' << k <<'\t'<< '\t' << Tree[k]->weigth << '\t' <<Tree[k]->parent<< '\t' <<Tree[k]->lchild<<'\t'<<Tree[k]->rchild<< endl;
}
return *Tree;
}
//这里需要找到一个能够将数组中找出权值最小的两颗树,并删除此两颗树 ,然后生成一个权值为二者之和的新树 整体-2+1 当树的数目为1是停止。
//找到树组前j个权值最小的两个
void SortTree(Huffman *Tree,int j,int &min1,int &min2)
{
min1 = find_D_min(Tree, j);
min2 = find_D_min(Tree, j);
//return 0;
}
//找到前k个树中最小的一个 然后返回该数组序号
Status find_D_min(Huffman *tree, int j)
{
int min, min_wei, i = 0;
while (tree[i]->parent != -1) //找到第一个合法值 好和后面做比较
{
i++;
}
min_wei = tree[i]->weigth; //相当于把一堆树中第一个合法的权值赋予它 然后排序找最小zhi
min = i; //此时数组序号
for (; i < j; i++)
{
if (tree[i]->weigth < min_wei&&tree[i]->parent == -1)
{
min_wei = tree[i]->weigth;
min = i;
}
}
tree[min]->parent = 0; //排除最小的一个
return min;
}
//从叶子点到根结点遍历二叉树 生成哈夫曼编码
Status HuffmanCode1(Huffman *Tree, Huffmancode &H_Code,int &sum)
{
cout << "Traversing Huffman tree from root node to leaf node" << endl;
H_Code = (Huffmancode)malloc(sum * sizeof(char *));
char *cd = (char*)malloc(sum * sizeof(char));
cd[sum - 1] = '\0';//哈夫曼编码最长为需要编码不同字符-1;
int i;
for (i = 0; i < sum; i++)
{
int current = i;
int father = Tree[i]->parent;
int start = sum - 1;
while (father != -1)
{
if (Tree[father]->lchild == current)
{
cd[--start] = '0';
}
else
{
cd[--start] = '1';
}
current = father;
father = Tree[father]->parent;
}
H_Code[i] = (char *)malloc((sum - start) * sizeof(char));
strcpy(H_Code[i], cd + start);
}
for (int i = 0; i < sum; ++i) {
cout << H_Code[i] << endl;;
}
free(cd);
return 0;
}
//从根结点到叶子结点遍历哈弗曼树 生成哈夫曼编码
Status HuffmanCode2(Huffman *Tree,Huffmancode &H_Code,int &sum)
{
cout <<"Traversing Huffman tree from leaf node to root node" << endl;
H_Code = (Huffmancode)malloc(sum * sizeof(char *)); //保持编码信息
char *cd = (char*)malloc(sum * sizeof(char)); //生成编码的过渡信息
int *a = (int *)malloc((2 * sum - 2 + 1) * sizeof(int)); //保存权值信息
int cur = 2 * sum - 2;
int cd_len = 0;
for (int i = 0; i < cur + 1; i++) //以权值作为信息
{ //权值信息都为0 然后用数组a保存原权值
a[i] = Tree[i]->weigth;
Tree[i]->weigth = 0;
}
//这里要好好看看
//编码过程
while (cur != -1)
{
if (Tree[cur]->weigth == 0)//权值为0时说明没有进行遍历
{
Tree[cur]->weigth = 1;//权值为1 说明进行了左遍历
if (Tree[cur]->lchild != -1)//NULL:空指针, 在C++中值为0,在C中定义为(void *)0.
{
cd[cd_len++] = '0';
cur = Tree[cur]->lchild;
}
else
{
cd[cd_len] = '\0';
H_Code[cur]= (char *)malloc((cd_len + 1) * sizeof(char));
strcpy(H_Code[cur],cd);
}
}
else if (Tree[cur]->weigth == 1)
{
Tree[cur]->weigth = 2;//权值为2进行了右遍历
if (Tree[cur]->rchild != -1)
{
cd[cd_len++] = '1';
cur = Tree[cur]->rchild;
}
}
else
{
//Tree[cur]->weigth = 0;
cur = Tree[cur]->parent;//结束标志
--cd_len;
}
}
for (int i = 0; i < 2 * sum - 2 + 1; i++)
{
Tree[i]->weigth= a[i];//还原权值
}
for (int i = 0; i < sum; ++i) {//输出编码
cout << H_Code[i] << endl;;
}
free(cd);
free(a);
return 0;
}
//从叶子结点返回wpl
Status WPL1(Huffman *Tree, int &sum)
{
int i, countRoads, WPL = 0;//如果需要知道每个叶子的带权路径长度 定义一个数组分别保存即可
//数组前sum都是信息
for (i = 0; i < sum; i++)
{
//每次都从结点从新开始计算wpl
countRoads = 0;
int father = Tree[i]->parent;
while (father != -1)
{
countRoads++;
father = Tree[father]->parent;//找到根结点就跳出 与编码过程不同的是 编码时需要有确认孩子是左孩子还是右孩子
}
WPL += countRoads*Tree[i]->weigth;//每次都累加
}
return WPL;
}
//从根结点到叶子
Status WPL2(Huffman *Tree, int &sum)
{
int countRoads=0,WPL=0;
int cur = 2 * sum - 2;//数组最大值
int *visit = (int *)malloc((2*sum-2)*sizeof(int));
for (int i = 0; i < cur+1; i++)
{
visit[i] = 0;
}
while (cur!= -1)//结束时返回到根部
{
if (visit[cur] == 0)
{
visit[cur] = 1;//遍历左路径 设置为1
if (Tree[cur]->lchild != -1)
{
//左孩子存在时
countRoads++;
cur = Tree[cur]->lchild;//路径加1 cur换孩子坐标
}
else
{
WPL += countRoads*Tree[cur]->weigth;
}
}
else if (visit[cur] == 1)
{
//已经遍历了左孩子 然后开始遍历右孩子
visit[cur] = 2;
if (Tree[cur]->rchild != -1)
{
countRoads++;
cur = Tree[cur]->rchild;
}
}
else
{
//左右孩子均遍历
visit[cur] = 0; //这个改不改都无所谓
cur = Tree[cur]->parent;
--countRoads;
}
}
return WPL;
}
int main()
{
//输入字符串(所有英文格式的字符串)
int sum = 0;
int *probabiblity = new int[128];
Enterstring(probabiblity, sum);
//创造哈夫曼树
Huffman *Tree = (Huffman*)malloc((2 * sum - 1) * sizeof(HTNODE));
CreatTree(Tree,probabiblity,sum);
//从根节点到叶子结点遍历二叉树 生成哈夫曼编码
Huffmancode H_Code;
HuffmanCode1(Tree, H_Code, sum);
HuffmanCode2(Tree, H_Code, sum);
//输出
cout << "Output Huffman tree" << endl; //没问题
cout << "character" << '\t' << "subscript" << '\t' << "weight" << '\t' << "parent" << '\t' << "lchild" << '\t' << "rchild" << endl;
for (int k = 0; k < 2 * sum - 1; k++)
{
cout << Tree[k]->symbol << '\t' << '\t' << k << '\t' << '\t' << Tree[k]->weigth << '\t' << Tree[k]->parent << '\t' << Tree[k]->lchild << '\t' << Tree[k]->rchild << endl;
}
cout << "WPL from root node to leaf node" << endl;
cout << WPL1(Tree, sum) << endl;
cout << "WPL from root leaf to node node" << endl;
cout << WPL2(Tree, sum) << endl;
return 0;
}
结果:
感觉哈夫曼还用了不少时间 一是能力还不够 二是拔牙然后周末摸鱼 白天学代码不自在 哎呀 希望寒假效率高一点 (可能吗@!!!??冲)
这次借鉴了一下大佬的代码
然后还有一些没有涉及到 (哈夫曼解码 用栈的结构 ) 后面再学学不同方法
哈夫曼报文编码和译码(涨姿势!!!)
哈夫曼树(C语言简单实现)
哈夫曼编码的C语言实现