本文是根据B站视频青岛大学 - 王卓老师的数据结构来实现的,涉及到哈夫曼Tree 和 哈夫曼Code的C++版完整实现,若有不足欢迎大佬斧正-(/▽\)
具体理论请配合B站视频来学习,构造哈夫曼Tree主要的方法如下:
第一步:构造森林全是根
第二步:选用两小造新树
第三步:删除两小添新人(parent设置为 n+1 到 2n-1 中的下标)
第四步:重复2、3步剩单根
话不多说,我们只需要记住这四步,把下面代码的框架敲熟了,就能运用自如了。
PS:代码有详细注解 和 引导思考,喜欢的话可以收藏一波~
#include
#include
#include
using namespace std;
typedef struct HFT
{
int weight;
int parent, LTree, RTree;
}HFT, *PHFT;
void Select(PHFT HFT, int n, int &s1, int &s2) //选取权值最小的两个结点
{
for(int i = 1; i < n; i++){ //初始化s1,s1的双亲为0
if(HFT[i].parent == 0){ /** 思考:为什么不是 i <= n **/
s1 = i; //解答:因为我们第 2n-1 个元素的parent是0
break; /** 思考:为什么要加上 break **/
}
}
for(int i = 1; i < n; i++){ //s1为权值最小的下标
if(HFT[i].parent == 0 && HFT[s1].weight > HFT[i].weight)
s1 = i;
}
for(int j = 1; j < n; j++){ //初始化s2,s2的双亲为0
if(HFT[j].parent == 0 && j != s1){
s2 = j;
break;
}
}
for(int j = 1; j < n; j++){ //s2为另一个权值最小的下标
if(HFT[j].parent == 0 && HFT[s2].weight > HFT[j].weight && j != s1)
s2 = j;
}
}
void initHFT(PHFT &H, int n)
{
if(n <= 1) return;
int m = 2*n - 1; //数组共2n - 1个元素
H = new HFT[m + 1]; //0号单元未用,H[m]表示根节点
for(int i = 1; i <= m; i++){
H[i].parent = 0;
H[i].LTree = 0;
H[i].RTree = 0;
}
cout << "please input the weight of nodes:" << endl;
for(int i = 1; i <= n; i++)
cin >> H[i].weight;
cout << endl;
for(int i = n + 1; i <= m; i++) //产生的新结点要放在从n+1开始,一直到2n-1的位置
{
int s1, s2;
Select(H, i, s1, s2);
H[s1].parent = i;
H[s2].parent = i; //相当于从表F中删除s1, s2
H[i].LTree = s1;
H[i].RTree = s2;
H[i].weight = H[s1].weight + H[s2].weight;
}
}
void showHFT(PHFT &H, int n)
{
cout << "index weight parent LTree RTree" << endl;
cout << left; //左对齐输出
int m = 2*n - 1;
for(int i = 1; i <= m; i++){
cout << setw(5) << i << " "; /** 思考: 为什么是setw(5) 和 setw(6) **/
cout << setw(6) << H[i].weight << " "; // 解答:当后面紧跟着的输出字段长度小于n的时候,在该字段前面用空格补齐;当输出字段长度大于n时,全部整体输出
cout << setw(6) << H[i].parent << " ";
cout << setw(6) << H[i].LTree << " ";
cout << setw(6) << H[i].RTree << " " << endl;
}
}
int main()
{
PHFT HFT;
int n = 0;
cout << "please input the number of nodes: ";
cin >> n;
initHFT(HFT, n);
showHFT(HFT, n);
system("pause");
return 0;
}
我们可以对应下面这张图来看代码,弄懂思路。
Input
please input the number of nodes: 7
please input the weight of nodes:
7
19
2
6
32
3
21
Output
index weight parent LTree RTree
1 7 0 0 0
2 19 11 0 0
3 2 8 0 0
4 6 9 0 0
5 32 12 0 0
6 3 8 0 0
7 21 12 0 0
8 5 9 3 6
9 11 10 8 4
10 18 11 1 9
11 37 13 10 2
12 53 13 7 5
13 90 0 11 12
该代码在实现哈夫曼编码核心算法时既使用了C++的string类来实现,也使用了C的方式实现。这是在学会构造哈夫曼树之后的进一步提升,在这里给需要提高的同学抛出一个思考问题,“C++如何处理模板类template实现自动根据用户输入的 weight 值类型来分配内存”。
首先先看图,根据图来实现以下步骤(用char动态数组):
第一步:构建哈夫曼树表、HC表(动态二维数组)、cd表(一维)
第二步:一般规定左子树路径为0,右子树路径为1,按哈夫曼树表寻找parent结点直到为0。
与第二步同时进行:先将临时cd表最后一个元素定为’\0’,创建临时结点记录当前处理的结点。
第三步:将cd表值赋值给HC表,同时销毁cd表的临时内存。
在这里插入代码片
```cpp
#include
#include
#include
#include
#include
using namespace std;
typedef struct HFT
{
float weight;
int parent, LTree, RTree;
string name;
}HFT, *PHFT;
void Select(const PHFT& H, const int& n, int& s1, int& s2){
for(int i = 1; i < n; i++){
if(H[i].parent == 0){
s1 = i;
break;
}
}
for(int i = 1; i < n; i++){
if(H[i].parent == 0 && H[s1].weight > H[i].weight)
s1 = i;
}
for(int j = 1; j < n; j++){
if(H[j].parent == 0 && j != s1){
s2 = j;
break;
}
}
for(int j = 1; j < n; j++){
if(H[j].parent == 0 && H[s2].weight > H[j].weight && j != s1)
s2 = j;
}
}
void initHFC(PHFT& HT, const int& n)
{
if(n <= 1) return;
int m = 2*n - 1;
HT = new HFT[m + 1];
for(int i = 1; i <= m; i++){
HT[i].parent = 0;
HT[i].LTree = 0;
HT[i].RTree = 0;
}
cout << "please input the weight of nodes and nodes' name as 0.23 A: " << endl;
for(int i = 1; i <= n; i++){
cin >> HT[i].weight >> HT[i].name;
}
cout << endl;
for(int i = n+1; i <= m; i++){
int s1, s2;
Select(HT, i, s1, s2);
HT[s1].parent = i;
HT[s2].parent = i;
HT[i].LTree = s1;
HT[i].RTree = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
}
/** char 类型解决方案 */
void ch_CreateHFMcode(const PHFT& HT, char** HC, const int& n)
{
char* temp = new char[n];
temp[n-1] = '\0';
int start = 0, c = 0, father = 0;
for(int i = 1; i <= n; i++)
{
start = n - 1;
c = i; //记录正在处理的当前位置
father = HT[i].parent;
while(father != 0) //从叶子节点向上回溯
{ //回溯一次 start指向前一个位置一个
if(HT[father].LTree == c) temp[--start] = '0';
else temp[--start] = '1';
c = father; //当前位置移到父节点
father = HT[father].parent; //更新父节点,继续向上回溯
}
HC[i] = new char[n-start]; // 为第 i 个字符串编码分配空间
strcpy(HC[i], &temp[start]); // 将求得的编码从临时空间cd复制到HC的当前行中,strcpy遇到 \0 拷贝就会结束
}
delete temp;
}
/** string 类型的解决方案 **/
void str_CreateHFMcode(PHFT& H, string *HC, const int& n)
{
string temp;
stack<string> st; //利用栈实现上一个char类型strcpy的方法
int cur = 0, father = 0;
for(int i = 1; i <= n; i++){
cur = i;
father = H[i].parent;
while(father != 0)
{
if(H[father].LTree == cur) st.push("0");
else st.push("1");
cur = father;
father = H[father].parent;
}
while(!st.empty()){
temp += st.top();
st.pop();
}
HC[i] = temp;
temp.erase(); //擦除内存
}
}
void showdata(const PHFT& HFT, char** HC, const int& n){
cout << "index weight parent LTree RTree" << endl;
cout << left;
int m = 2*n - 1;
for(int i = 1; i <= m; i++){
cout << setw(5) << i << " ";
cout << setw(6) << HFT[i].weight << " ";
cout << setw(6) << HFT[i].parent << " ";
cout << setw(6) << HFT[i].LTree << " ";
cout << setw(6) << HFT[i].RTree << " " << endl;
}
cout << endl;
cout << "Name HFMCode" << endl;
for(int i = 1; i <= n; i++){
cout << setw(5) << HFT[i].name << " ";
cout << setw(7) << HC[i] << " " << endl;
}
}
int main()
{
PHFT HFT;
int n = 0;
cout << "please intput the number of vertices: ";
cin >> n;
char** HC = new char*[n];
initHFC(HFT, n);
ch_CreateHFMcode(HFT, HC, n);
showdata(HFT, HC, n);
return 0;
}
Input
please intput the number of vertice: 7
what value do you want to give them?
0.4
0.3
0.15
0.05
0.04
0.03
0.03
Output
index HC[i]
1 0
2 10
3 110
4 11111
5 11110
6 11100
7 11101
路曼曼其修远兮,吾将上下而求索