目录
1.前言
目的:利用C++特性 用指针解决问题
注:由于编码、译码等功能函数是基于Huffman初始化、构造和遍历实现的,此处不再进行展示,仅展示关键部分Huffman构造和遍历函数,对关键部分进行解读
声明:笔者受前人启发,这篇博客部分是对前辈代码的解读,也有部分是笔者在前人代码基础上略作改动而得。
原作者传送门:哈夫曼树的建立_Reticent_Man的博客-CSDN博客_创建哈夫曼树
2.节点
template<class T>
struct AlphaNode {
T data;
int freq=0;
string code;//编码
AlphaNode<T>* parent;
AlphaNode<T>* lchild;
AlphaNode<T>* rchild;
};
1.使用模板类:便于后续存储自定义数据
2.freq:frequency的缩写。因为代码的输入端是输入任意字符串并做统计,于是在此处先声明用于存储统计结果的数据域。
3.code:编码结果(在本篇博客中不做展示编码部分内容)
4.节点遵循三叉树结构:根节点和左右子树。
3.字符统计
//字符串统计函数
//需要注意 这里是从arr_str[1]开始放东西的 arr_str[0]是空的
AlphaNode<char> arr_str[400];//建立一个节点数组
static int length = 1;
void statisticAlpha(string str) {
int i = 0;//从string[0]开始遍历
while (str[i]) {
if (0 == i) {
arr_str[i+1].data = str[i];
arr_str[i+1].freq++;
length++;
}
else {
int j = 1;//从新建数组的第二个存放数据的节点开始遍历
//判定是否有重复的数据
for (; arr_str[j].data!=NULL; j++) {
if (str[i] == arr_str[j].data)break;
}
//锁定应该存放东西的位置 即j位置
if (j >length-1) {
arr_str[length].data = str[i];
arr_str[length].freq++;
length++;
}
else {
arr_str[j].freq++;
}
}
i++;
}
int j = 1;
cout << "您输入的字符串 统计为:" << endl;
while (arr_str[j].data!=NULL) {
cout << arr_str[j].data << ':' << arr_str[j].freq << endl;
j++;
}
}
//注意 以下代码只是示例
main(){
cout <<"请在下方输入一个任意字符串"<< endl;
string temp;
getline(cin, temp);
statisticAlpha(temp);
}
1.在main()函数中用temp来暂存(最后的示例)
读取的时候要求可以读取空格、以回车结尾,故 使用getline(cin,str)函数。
2.特判:(0==i)只是因为第一个必定没有与之匹配的字符,所以直接进行存储。
3.
//判定是否有重复的数据
for (; arr_str[j].data!=NULL; j++) {
if (str[i] == arr_str[j].data)break;
}
//锁定应该存放东西的位置 即j位置
由于str[]是暂时用于存储的数组,暂时不会变动,所以把它放在前面。
当比较到结果的时候 由于数组从1开始存东西 所以循环结果j可以直接拿到下面去使用,因为在正常位置[j]是空的。
4.下面就是特判j是不是到末尾了,然后进行一系列相关操作。
4.挑选两个最小值
【要点】
1.最小值是随着数组增长而变动的,新节点 (由两个孩子的freq)的加和也会被纳入计算范围
eg:第一次:2 1 1
第二次:2 1 1 2(末尾的2=1+1)(两个最小权值(freq)的孩子加和)
第三次:2 1 1 2 4(末尾的4=2+2)(由于两个1 已经用了,最小权值轮到老的2和新的2了)
2.如何判断某个节点是否已经作为孩子?
引入哨兵p[200];
int p[200] = { 0 };//用来判断节点是否被选取过
哨兵先全部初始化为0,表示没有被选取过
3.
void Huffman::SelectMin(int &x, int &y,int end) {
//x、y用于返回两个 最小值权值的 位置
long int min1 = 999999;
for (int i = 1; i <= end; i++) {
if (a[i].freq < min1 && p[i] == 0) {
min1 = a[i].freq;
x = i;
}
}
min1 = 999999;
for (int i = 1; i <= end; i++) {
if (a[i].freq < min1 && i != x && p[i] == 0) {
min1= a[i].freq;
y = i;
}
}
}
注意到两个特判中 都有p[i]==0 //为了保证没有被选取
定义一个最小值 初始化为999999,以保证会有最小值产生
4.为什么传引用int &x,int &y
便于随时更改数据,因为到了后面部分代码的位置不同,作用域不一样,为避免麻烦采用引用的方式。
5.end是什么
前面1. 有讲到:比较的长度会不断变长。这一次的end在后面会传入原长度,但下一次会传入length+1(因为新的节点产生了,要将新节点纳入比较)所以end是个不定长数字。
5.构建Huffman树
//示例:避免看不懂在写什么
class Huffman{
public:
AlphaNode<char>* Creation(int);//创建哈夫曼树
private:
AlphaNode<char>* a;
}
int x, y;
//构建的函数正文:
AlphaNode<char>* Huffman::Creation(int n) {
int i;
//初始化
for (int i = 1; i <= 2 * n - 1; i++) {
a[i].lchild = a[i].parent = a[i].rchild = NULL;
}
for (i = n; i < 2 * n - 2; i++) {
SelectMin(x, y, i-1);
p[x] = p[y] = 1;
a[i].lchild = &a[x];
a[i].rchild = &a[y];
a[i].freq = a[x].freq + a[y].freq;
}
return &a[i - 1];
}
//示例:
main(){
AlphaNode<char> *p = hf.Creation(length);
//length就是存储之后的存储数组的长度 比如abca的长度为4(从1开始存 +1)
}
函数的参数int n就是数组长度(后面示例里面传入)
着重讲解中间一段代码:
1. SelectMin( x , y , i-1 );
确定最小权值节点的位置 并且返回为x、y,其中x节点权值≤y节点权值
(默认x为左节点)
2.(i < 2*n - 2)
为什么是这个判定?
这个循环的次数一定有严格限制
因为在最后一次循环的时候 两个倒数第一第二大的权重总会进行加和,而这种加和会产生一个新的存储点,即:
a[i].freq = a[x].freq + a[y].freq;
这个句子仍然会运行。
如果次数比这个多一次,那么就会造成y值无法确定,从而进入死循环
(因为传的是y的引用,而SelectMin函数也没有写明“如果y没有匹配的值”会如何,结果是y会继续用原来的地址值继续这个for循环)
循环次数=数组长度 - 1 ;
字符串abca 统计后的数组长度为3(合并同类项) 那么比较次数为3-1=2。
回到代码里,比较次数为(2*n - 2 - n)+1=n - 1。
3.为什么是renturn &a[i - 1];?
因为按照这个算法:直接往数组后面开辟空间存东西
a数组前面的部分(a[1] ~a[n])都是存的数据,没有孩子和根节点。
从a[n+1]开始才有的指针运算,有孩子和根节点的连接
而这个数组的末尾,即最终加权和节点为头结点。
头结点存储了相应的孩子节点。
所以直接返回它的地址。
6.遍历函数
void Huffman::DispTree(AlphaNode<char>* a) {
if (a != NULL) {
cout << a->data;
if (a->lchild != NULL || a->rchild != NULL) {
cout << '(';
DispTree(a->lchild);
if (a->rchild != NULL)cout << ',';
DispTree(a->rchild);
cout << ')';
}
}
}
引用原作者的话:
后来发现纯数字的输出形式看不出子树与双亲之间的对应关系。翻阅数据结构书时,看到广义表,于是我想到用加括号的方法,即先判断一个节点是否有左子树(在赫夫曼树中有左子树就一定有右子树),如果有就输出括号,左右子树之间用逗号相隔。这样便增加了直观可读性。
7.源代码
#include<iostream>
#include<string>
using namespace std;
template<class T>
struct AlphaNode {
T data;
int freq=0;
string code;//编码
AlphaNode<T>* parent;
AlphaNode<T>* lchild;
AlphaNode<T>* rchild;
};
AlphaNode<char> arr_str[400];
int p[200] = { 0 };//用来判断节点是否被选取过
void statisticAlpha(string str);
int x, y;
struct HCode {
char data;
string code;
};
class Huffman {
private:
AlphaNode<char>* a;
int N;//叶子节点数量
void code(int i, string newcode);//递归函数 对应第i个节点编码
public:
Huffman(AlphaNode<char>*);
AlphaNode<char>* Creation(int);//创建哈夫曼树
void DispTree(AlphaNode<char>*);//遍历
void CreateCodeTable();//创建编码表
void Encode(char *s, char *d);//编码
void Decode(char *s, char *d);//译码
void SelectMin( int &x, int &y, int end);
};
Huffman::Huffman(AlphaNode<char>* temp) {
a = temp;
}
//字符串统计函数
//需要注意 这里是从arr_str[1]开始放东西的 arr_str[0]是空的
static int length = 1;
void statisticAlpha(string str) {
int i = 0;//从string[0]开始遍历
while (str[i]) {
if (0 == i) {
arr_str[i+1].data = str[i];
arr_str[i+1].freq++;
length++;
}
else {
int j = 1;//从新建数组的第二个存放数据的节点开始遍历
for (; arr_str[j].data!=NULL; j++) {
if (str[i] == arr_str[j].data)break;
}
//锁定应该存放东西的位置
if (j >length-1) {
arr_str[length].data = str[i];
arr_str[length].freq++;
length++;
}
else {
arr_str[j].freq++;
}
}
i++;
}
int j = 1;
cout << "您输入的字符串 统计为:" << endl;
while (arr_str[j].data!=NULL) {
cout << arr_str[j].data << ':' << arr_str[j].freq << endl;
j++;
}
}
//返回统计后的数组的长度
//挑选两个最小值的函数
void Huffman::SelectMin(int &x, int &y,int end) {
//x、y用于返回两个 最小值权值的 位置
long int min1 = 999999;
for (int i = 1; i <= end; i++) {
if (a[i].freq < min1 && p[i] == 0) {
min1 = a[i].freq;
x = i;
}
}
min1 = 999999;
for (int i = 1; i <= end; i++) {
if (a[i].freq < min1 && i != x && p[i] == 0) {
min1= a[i].freq;
y = i;
}
}
}
//abbcccddddeeeeeffffffggggggg
AlphaNode<char>* Huffman::Creation(int n) {
int i;
//初始化
for (int i = 1; i <= 2 * n - 1; i++) {
a[i].lchild = a[i].parent = a[i].rchild = NULL;
}
for (i = n; i < 2 * n - 2; i++) {
SelectMin(x, y, i-1);
p[x] = p[y] = 1;
a[i].lchild = &a[x];
a[i].rchild = &a[y];
a[i].freq = a[x].freq + a[y].freq;
}
return &a[i - 1];
}
void Huffman::DispTree(AlphaNode<char>* a) {
if (a != NULL) {
cout << a->data;
if (a->lchild != NULL || a->rchild != NULL) {
cout << '(';
DispTree(a->lchild);
if (a->rchild != NULL)cout << ',';
DispTree(a->rchild);
cout << ')';
}
}
}
int main() {
cout <<"请在下方输入一个任意字符串"<< endl;
string temp;
getline(cin, temp);
statisticAlpha(temp);
cout << length << endl;
Huffman hf(arr_str);
AlphaNode<char> *p = hf.Creation(length);
hf.DispTree(p);
return 0;
}