需要注意的是:
strcpy函数原型:char* strcpy(char* des,const char* source)
两个参数都是地址类型的
注意类型转换,保证类型一致匹配。
在两种解法中,Select函数的编写是借鉴csdn大佬的思路,非个人原创。
第一种解法:
#include<iostream>
#include<string.h>
# define MAX 99999
using namespace std;
typedef struct HTree
{
int weight;//权值
int parent, lchild, rchild;
}HTNode, * HuffmanTree;//一个*表示一级指针
typedef char** HuffmanCode;//char**二级指针,就是有两个门牌号,
//哈夫曼编码就像是一个锯齿状的二维数组,对于这个数组的大小我们不知道,这就需要动态申请(不浪费空间,灵活)
//还有,数组中的每一个,存储的是一个哈夫曼编码,没一个哈夫曼编码的长度我们也不知道,这也需要动态申请(指针)
//所以设置为二级指针就很好了。我们通过第一个门牌号找到了所有编码的位置,通过第二个门牌号,
//找到了每一个编码的位置。最终可以准确地到每一个编码。
void Select(HuffmanTree ht, int n, int *s1, int *s2);
void CreateHuffmanTree(HuffmanTree & HT, int n);
void CreatHuffmanCode(HuffmanTree HT, HuffmanCode& HC, int n);
int m,i;
void Select(HuffmanTree ht, int n, int *s1, int *s2)
{
int i, min1 = MAX, min2 = MAX;//求最大最小的常用套路
*s1 = 0;
*s2 = 0;
for (i = 1; i <= n; i++)
{
if (ht[i].parent == 0) //双亲为0表明没有被创建过,代表是根节点。不是0表明被创建过,拥有双亲,是孩子节点。控制了选择范围。
{
if (ht[i].weight < min1)
{
min2 = min1;
*s2 = *s1;
min1 = ht[i].weight;
*s1 = i;
}
//这一段代码,先更新第二小,在更新第一小。因为在条件ht[i].weight < min1满足的时候,
//就表明第一小要被更新。那么显然,原来的第一小就变成了第二小。
//在更新第一小的时候,必须更新第二小。否则得不到第二小,或者得到的是错误的。
//第一小和第二小是有关系的,二者在寻找过程中,是变动的,动态的,持续更新,直到确定。
else if (ht[i].weight < min2)
{
min2 = ht[i].weight;
*s2 = i;
}
}
}
}
//Select函数中最最重要的两行代码就是:min2 = min1;*s2 = *s1;
//
void CreateHuffmanTree(HuffmanTree &HT,int n)
{
//执行树的构造的前提条件
if (n < 1) return;
m = 2 * n - 1;
HT = new HTNode[m + 1];//m+1=2n个空间:0号单元不使用,n个叶子结点,n-1个新创建出来的结点。HT[m]是根节点
//初始化,便于标记记录
//刚开始,每个叶子节点都是独立的,都是自己的根,都没有双亲,没有孩子。所以 HT[i].parent = 0; HT[i].lchild = 0; HT[i].rchild = 0;
for(i=1;i<=m;++i)
{
HT[i].parent = 0; HT[i].lchild = 0; HT[i].rchild = 0;
}
//输入数据(只有叶子节点)
for(i=1;i<=n;++i)
{
cin >> HT[i].weight;
}
//对新节点进行创建,进行哈夫曼树的构造
int s1, s2;
//拥有n个叶子结点的哈夫曼树共有2*n-1个节点。2*n-1=n+(n-1) 前者n代表原来的叶子节点,n-1代表新构造出来的节点。
//根据哈夫曼树的构造原则就可以知道,每次从现有的所有结点中挑选出权值最小的两个节点进行构造,得到一个新节点。
//这个新结点是被挑选出的两个节点的双亲 ,被挑选的节点是这个节点的孩子。
//第一种理解方法:第一次挑选的时候,原节点:n-2 新节点:1 (暂时不加入前者) 再进行第二次挑选的时候(仅仅只是记录结点的数目)
//我们需要得到两个节点,通过第一次已经得到一个,所以每次我们只需要在得到一个节点即可(仅考虑数目)
//那么剩余的n-2个节点,每次挑选一个,我们就需要挑选n-2次,再加上第一次,共挑选n-1次。
for(i=n+1;i<=m;++i)//通过n-1次的选择,删除,合并来创建哈夫曼树。
{
Select(HT, i - 1, &s1, &s2);//其中第二个参数,是进行挑选的次数。i-1=n次??怎么不是n-1次?
//后两个参数是我们需要从叶子节点中找到min1 min2并且返回他们的位置,要使用引用传递。以便进行操作。
HT[s1].parent = i; HT[s2].parent = i;//双亲
HT[i].lchild = s1; HT[i].rchild = s2;//孩子
//所有节点共有2*n-1个,我们开辟了一个数组大小为2*n
//(对于数组来说,你要时刻记住,如果数组大小为n,那么存储的下标范围是0~n-1,最大的存储下表是n-1)
//0号单元不使用,1~n个单元存储的是叶子,n+1~2*n-1
//每创建一个节点,就赋予他一个下标。n+1~2*n-1
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
}
void CreatHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n)
{
HC = new char* [n + 1];//指针数组 仅仅是门牌号,还不是存储数据的房间
char* cd = new char[n];//临时空间:让最后一个编码先存放,从后面开始存放。
//那么后半份空间被使用,前面的空间没有用。
cd[n - 1] = '\0';//字符串结束标志
for(i=1;i<=n;++i)//得到所有编码
{
int start = n - 1;
int c = i;
int f = HT[i].parent;
//得到一个叶子的编码
while (f != 0)//while的条件!!!
{
--start;
if (HT[f].lchild == c) cd[start] = '0';
else cd[start] = '1';
c = f;//向上移动
f = HT[f].parent;//向上移动
}
//将临时空间中的编码存入真正需要的空间
HC[i] = new char[n - start];//申请存储空间
strcpy(HC[i], &cd[start]);
}
//全部编码完成,删除不需要的申请空间
delete cd;
}
int main()
{
int n=5,i;
HuffmanTree HTR = NULL;
CreateHuffmanTree(HTR, 5);
HuffmanCode hc = NULL;
CreatHuffmanCode(HTR, hc, 5);
cout<<"各结点的值:"<<endl;
for(i=1;i<=9;i++)
{
cout<<HTR[i].weight<<" ";
}
cout<<endl;
cout<<"编码:"<<endl;
for(i=1;i<=5;i++)
{
cout << hc[i]<< endl;
}
return 0;
}
第二种解法:
//#pragma once
#include<iostream>
#include<string.h>
# define MAX 99999
using namespace std;
typedef struct HTree
{
int weight;
int parent, lchild, rchild;
}HTNode, * HuffmanTree;
typedef char** HuffmanCode;
void Select(HuffmanTree ht, int n, int *s1, int *s2);
void CreateHuffmanTree(HuffmanTree & HT, int n);
void CreatHuffmanCode(HuffmanTree HT, HuffmanCode& HC, int n);
int m,i;
void Select(HuffmanTree ht, int n, int &s1, int &s2)
{
int i, min1 = MAX, min2 = MAX;
s1 = 0;
s2 = 0;
for (i = 1; i <= n; i++)
{
if (ht[i].parent == 0)
{
if (ht[i].weight < min1)
{
min2 = min1;
s2 = s1;
min1 = ht[i].weight;
s1 = i;
}
else if (ht[i].weight < min2)
{
min2 = ht[i].weight;
s2 = i;
}
}
}
}
//笨方法,但是易于理解,得出的结果也没有错。
//思路:先遍历一次,找到最小的。再把最小的剔除。在遍历一次,找到此时的最小的(其实是第二小)
//这其中的重点就是如何剔除!!!!!不要忘了我们的目的,是为了找到两个最小的节点,进行树的创建。
void Select1(HuffmanTree ht, int n, int &s1, int &s2)
{
int i, min1 = MAX, min2 = MAX;
s1 = 0;
s2 = 0;
for (i = 1; i <= n; i++)
{
if (ht[i].parent == 0)
{
if (ht[i].weight < min1)
{
min1 = ht[i].weight;
s1 = i;
}
}
}
for (i = 1; i <= n; i++)
{
if (ht[i].parent == 0)
{
if (ht[i].weight < min2&&i!=s1)// ht[i].weight !=min1 选取节点构造哈夫曼树,不是单纯找最小
{
min2 = ht[i].weight;
s2= i;
}
}
}
}
void CreateHuffmanTree(HuffmanTree &HT,int n)
{
if (n < 1) return;
m = 2 * n - 1;
HT = new HTNode[m + 1];
for(i=1;i<=m;++i)
{
HT[i].parent = 0; HT[i].lchild = 0; HT[i].rchild = 0;
}
for(i=1;i<=n;++i)
{
cin >> HT[i].weight;
}
//对新节点进行创建,进行哈夫曼树的构造
int s1, s2;
for(i=n+1;i<=m;++i)
{
Select1(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 CreatHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n)
{
HC = new char* [n + 1];
char* cd = new char[n];
cd[n - 1] = '\0';
for(i=1;i<=n;++i)
{
int start = n - 1;
int c = i;
int f = HT[i].parent;
//得到一个叶子的编码
while (f != 0)
{
--start;
if (HT[f].lchild == c) cd[start] = '0';
else cd[start] = '1';
c = f;//向上移动
f = HT[f].parent;
}
HC[i] = new char[n - start];
strcpy(HC[i], &cd[start]);
}
delete cd;
}
void printHtree(HuffmanTree ht)
{
cout<<"各结点的值:"<<endl;
for(i=1;i<=9;i++)
{
cout<<"No.:"<<i
<<" wt:"<<ht[i].weight<<" parent:"<<ht[i].parent<<" rchild:"<<ht[i].rchild<<" lchild:"<<ht[i].lchild<<endl;;
}
cout<<endl;
}
void printHtreeCode(HuffmanCode hc)
{
cout<<"编码:"<<endl;
for(int i=1;i<=5;i++)
{
cout << hc[i]<< endl;
}
}
int main()
{
int n=5;
HuffmanTree HTR=NULL;
CreateHuffmanTree(HTR, 5);
printHtree(HTR);
HuffmanCode hc = NULL;
CreatHuffmanCode(HTR, hc, 5);
printHtreeCode(hc);
return 0;
}
小新还在努力,文章中如有错误,请多多指教。