数据结构与算法12——哈夫曼
2020-04-30 本文已影响0人
Foxhoundsun
概念
最优二叉树也称为哈夫曼树(Huffman),是指对于一组带有确定权值的叶子结点,构造的具有最小带权路径长度的二叉树。
路径是从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径
路径长度是路径上的分支数目称作路径长度
二叉树的路径长度是指由根节点到所有叶子结点的路径长度之和。
权值是指一个与特定结点相关的数值。
结点的带权路径长度是从该结点倒数根之间的路径长度和结点上权的乘积
树的带权路径长度是树中所有叶子结点的带权路径长度之和
构造哈夫曼树
1. 构造过程
构造哈夫曼树时,首先选择权小的,保证权大的离根较近,这样在计算树的带权路径长度时,就会得到最小带权路径长度,这种生成算法是典型的贪心法
。算法的实现主要分两大部分:
1.1 初始化
先动态申请2n
个单元,然后循环2n-1
次,从0
号单元开始,依次将0至2n-1
所有单元中的双亲、左孩子、右孩子的下标初始化,然后再循环n
次,输入前n
个单元中叶子结点的值。
1.2 创建树
循环n-1
次,通过n-1
次的选择、删除与合并来创建哈夫曼树。
2. 数据模型
//初始设定的权值最大值
const int MaxValue = 10000;
//初始设定的最大编码位数
const int MaxBit = 4;
//初始设定的最大结点个数
const int MaxN = 10;
typedef struct HaffNode{
int weight;
int flag;
int parent;
int leftChild;
int rightChild;
}HaffNode;
//存放哈夫曼编码的数据元素结构
typedef struct Code
{
//数组
int bit[MaxBit];
//编码的起始下标
int start;
//字符的权值
int weight;
}Code;
3. 构造
//1.
//根据权重值,构建哈夫曼树;
//{2,4,5,7}
//n = 4;
void Haffman(int weight[],int n,HaffNode *haffTree){
int j,m1,m2,x1,x2;
//1.哈夫曼树初始化
//n个叶子结点. 2n-1
for(int i = 0; i < 2*n-1;i++){
if(i<n)
haffTree[i].weight = weight[i];
else
haffTree[i].weight = 0;
haffTree[i].parent = 0;
haffTree[i].flag = 0;
haffTree[i].leftChild = -1;
haffTree[i].rightChild = -1;
}
//2.构造哈夫曼树haffTree的n-1个非叶结点
for (int i = 0; i< n - 1; i++){
m1 = m2 = MaxValue;
x1 = x2 = 0;
//2,4,5,7
for (j = 0; j< n + i; j++)//循环找出所有权重中,最小的二个值--morgan
{
if (haffTree[j].weight < m1 && haffTree[j].flag == 0)
{
m2 = m1;
x2 = x1;
m1 = haffTree[j].weight;
x1 = j;
} else if(haffTree[j].weight<m2 && haffTree[j].flag == 0)
{
m2 = haffTree[j].weight;
x2 = j;
}
}
//3.将找出的两棵权值最小的子树合并为一棵子树
haffTree[x1].parent = n + i;
haffTree[x2].parent = n + i;
//将2个结点的flag 标记为1,表示已经加入到哈夫曼树中
haffTree[x1].flag = 1;
haffTree[x2].flag = 1;
//修改n+i结点的权值
haffTree[n + i].weight = haffTree[x1].weight + haffTree[x2].weight;
//修改n+i的左右孩子的值
haffTree[n + i].leftChild = x1;
haffTree[n + i].rightChild = x2;
}
4. 哈夫曼编码
依次以叶子为出发点,向上回溯至根结点为止。回溯时走左分支则生成代码0,走右分支则生成代码1。
由n个结点的哈夫曼树haffTree构造哈夫曼编码haffCode
//{2,4,5,7}
*/
void HaffmanCode(HaffNode haffTree[], int n, Code haffCode[])
{
//1.创建一个结点cd
Code *cd = (Code * )malloc(sizeof(Code));
int child, parent;
//2.求n个叶结点的哈夫曼编码
for (int i = 0; i<n; i++)
{
//从0开始计数
cd->start = 0;
//取得编码对应权值的字符
cd->weight = haffTree[i].weight;
//当叶子结点i 为孩子结点.
child = i;
//找到child 的双亲结点;
parent = haffTree[child].parent;
//由叶结点向上直到根结点
while (parent != 0)
{
if (haffTree[parent].leftChild == child)
cd->bit[cd->start] = 0;//左孩子结点编码0
else
cd->bit[cd->start] = 1;//右孩子结点编码1
//编码自增
cd->start++;
//当前双亲结点成为孩子结点
child = parent;
//找到双亲结点
parent = haffTree[child].parent;
}
int temp = 0;
for (int j = cd->start - 1; j >= 0; j--){
temp = cd->start-j-1;
haffCode[i].bit[temp] = cd->bit[j];
}
//把cd中的数据赋值到haffCode[i]中.
//保存好haffCode 的起始位以及权值;
haffCode[i].start = cd->start;
//保存编码对应的权值
haffCode[i].weight = cd->weight;
}
}
5. 调用
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, 哈夫曼编码!\n");
int i, j, n = 4, m = 0;
//权值
int weight[] = {2,4,5,7};
//初始化哈夫曼树, 哈夫曼编码
HaffNode *myHaffTree = malloc(sizeof(HaffNode)*2*n-1);
Code *myHaffCode = malloc(sizeof(Code)*n);
//当前n > MaxN,表示超界. 无法处理.
if (n>MaxN)
{
printf("定义的n越界,修改MaxN!");
exit(0);
}
//1. 构建哈夫曼树
Haffman(weight, n, myHaffTree);
//2.根据哈夫曼树得到哈夫曼编码
HaffmanCode(myHaffTree, n, myHaffCode);
//3.
for (i = 0; i<n; i++)
{
printf("Weight = %d\n",myHaffCode[i].weight);
for (j = 0; j<myHaffCode[i].start; j++)
printf("%d",myHaffCode[i].bit[j]);
m = m + myHaffCode[i].weight*myHaffCode[i].start;
printf("\n");
}
printf("Huffman's WPS is:%d\n",m);
return 0;
}
6. 输出结果
Weight = 2
110
Weight = 4
111
Weight = 5
10
Weight = 7
0
Huffman's WPS is