首先便是理解什么是前缀编码,不等长编码,理解数据压缩的基本方法。
哈夫曼编码是一种十分有效的编码方法,广泛用于数据压缩中,其压缩率通常在 20%~90% 之间。采用不等长编码使编码不会有歧义也就是不二义性,并且和等长编码相比更加节省空间。
贪心算法实现贪心算法主要思路通过每次查找最小权和次小权构建叶子节点,和作为父节点的权值,如此循环下来构成一棵完全二叉树,对应的左子树编码为0,右子树编码为1,通过遍历树即可得出最终的哈夫曼编码。
以下是贪心算法的代码段(PS:最后有所有代码)
int x1, x2, w1, w2;
for (int i = 0; i < H - 1; ++i) {
x1 = x2 = -1;
w1 = w2 = MAXWEIGHT;
for (int j = 0; j < H + i; ++j) {//H + i次里面遍历得到最小权重的节点
if (haffman[j].parent == -1 && haffman[j].weight < w1) {
w2 = w1;
x2 = x1;
x1 = j;
w1 = haffman[j].weight;
}
else if(haffman[j].parent == -1 && haffman[j].weight < w2) {
x2 = j;
w2 = haffman[j].weight;
}
}
haffman[H + i].leftChild = x2;
haffman[H + i].rightChild = x1;
haffman[H + i].weight = w1 + w2;
haffman[x1].parent = H + i;
haffman[x2].parent = H + i;
}
}
这部分代码在循环里在嵌套一个循环,遍历得最小的两个节点。如果每次最小的更新了,那么需要把上次最小的给第二个最小的。两个循环比较复杂,难以理解,每次合成新的节点后都会更新最小的数,if语句同时需要实现复制交换的功能。
这里体现的就是贪心算法
主要思路通过每次查找最小权和次小权构建叶子节点,和作为父节点的权值,如此循环下来构成一棵完全二叉树,对应的左子树编码为0,右子树编码为1,通过遍历树即可得出最终的哈夫曼编码。
输出结果的示例
用二叉数对编码进行表示(手写,有点潦草,嘻嘻)
附上完整代码
#include <iostream>
using namespace std;
//最大字符编码数组长度
#define MAXCODELEN 100
//最大哈夫曼节点结构体数组个数
#define MAXHAFF 100
//最大哈夫曼编码结构体数组的个数
#define MAXCODE 100
#define MAXWEIGHT 10000;
typedef struct Haffman {
int weight; //权重
char ch; //字符
int parent;//父节点
int leftChild;//左孩子节点
int rightChild;//右孩子节点
} HaffmaNode;
typedef struct Code {
int code[MAXCODELEN];//字符的哈夫曼编码的存储
int start;//从哪个位置开始
} HaffmaCode;
HaffmaNode haffman[MAXHAFF];
HaffmaCode code[MAXCODE];
void buildHaffman(int H) {//H是输入的需要哈夫曼编码的字符的个数
//哈夫曼节点的初始化之前的工作, weight为0,parent,leftChile,rightChile都为-1
for (int i = 0; i < H * 2 - 1; ++i) {
haffman[i].weight = 0;
haffman[i].parent = -1;
haffman[i].leftChild = -1;
haffman[i].rightChild = -1;
}
std::cout << "请输入需要哈夫曼编码的字符和权重大小" << std::endl;
for (int i = 0; i < H; i++) {
std::cout << "请分别输入第" << i +1<< "个哈夫曼字符和权重" << std::endl;
std::cin >> haffman[i].ch;
std::cin >> haffman[i].weight;
}
//每次找出最小的权重的节点,生成新的节点,需要H - 1 次合并
int x1, x2, w1, w2;
for (int i = 0; i < H - 1; ++i) {
x1 = x2 = -1;
w1 = w2 = MAXWEIGHT;
for (int j = 0; j < H + i; ++j) {//H + i次里面遍历得到最小权重的节点
if (haffman[j].parent == -1 && haffman[j].weight < w1) {
w2 = w1;
x2 = x1;
x1 = j;
w1 = haffman[j].weight;
}
else if(haffman[j].parent == -1 && haffman[j].weight < w2) {
x2 = j;
w2 = haffman[j].weight;
}
}
haffman[H + i].leftChild = x2;
haffman[H + i].rightChild = x1;
haffman[H + i].weight = w1 + w2;
haffman[x1].parent = H + i;
haffman[x2].parent = H + i;
}
}
void visCode(int H) {
HaffmaCode hCode;//保存当前叶子节点的字符编码
int curParent;//当前父节点
int c;//下标和叶子节点的编号
for (int i = 0; i < H; ++i) {
hCode.start = H - 1;
c = i;
curParent = haffman[i].parent;
while (curParent != -1) {/
if (haffman[curParent].leftChild == c) {
hCode.code[hCode.start] = 1;//左分支赋予0,右分支赋予1
} else {
hCode.code[hCode.start] = 0;
}
hCode.start--;
c = curParent;
curParent = haffman[c].parent;
}
for (int j = hCode.start + 1; j < H; ++j) {//把当前的叶子节点信息保存到编码结构体里面
code[i].code[j] = hCode.code[j];
}
code[i].start = hCode.start;
}
}
int main() {
std::cout << "请输入有多少个哈夫曼字符" << std::endl;
int H = 0;
std::cin >> H;
if ( H<= 0) {
std::cout << "您输入的个数有误" << std::endl;
}
buildHaffman(H);
visCode(H);
for (int i = 0; i < H; ++i) {
std::cout << haffman[i].ch << ": Haffman Code is:";//前缀码
for (int j = code[i].start + 1; j < H; ++j) {
std::cout << code[i].code[j];
}
std::cout << std::endl;
}return 0;
}