主题
代码实现哈夫曼树的创建,建立,构造,实现哈夫曼编码,实现思路和要点:
抓住哈夫曼树的性质,每轮选出2个权值最小的点进行构造树。
抓住哈夫曼编码的性质,从根出发,向左标0,向右标1。
各种语言的实现可以看下面这个:
哈夫曼树的各种语言java,python,c++,c,c#的实现
哈夫曼树的构造思路:
网上说得都挺复杂的,看了半天还是看不懂,所以自己写了个:
- 每轮选出2个权值最小的点,权值加和后,放到空的新位置,标注左孩子和右孩子节点。例如这里,第一轮中,
d和e
点最小,权值的和为5,于是把空位置6更新为权值为5,左孩子d右孩子e。 - 一直这样挑选,直到所有的点都被挑选过
used=1
,只剩下一个点没有被used
。 - 利用这个得到的表去构造哈夫曼树。这个过程其实可以放到一个
哈夫曼node集数组
中,用for循环,从0到nodeNum
遍历,一边遍历,一遍把左右节点连接上,最后返回Node[NodeNum-1]
,也就是这个没有被used过
的节点。显然,它是根节点。这个其实还挺巧妙的,从这个题得到的启发:【数据结构X.8】代码实现和算法解析 已知一颗树的层次序列及每个节点的度, 编写算法构造这个树的孩子兄弟链表 - 最后进行编码。由于
树的非递归后序遍历具有能保存其全部根节点的性质
,这是因为进行后序遍历时,最后才会访问祖先节点,所以,当访问x节点时,其所有的祖先节点都存在栈
里。利用这个性质,用栈保存从祖先节点,到X节点的路径,然后访问栈里的祖先节点,如果是左子树,标0,如果是右子树,就标1。
注意:
同一组节点,可以组成多个不同形状的哈夫曼树,他们的共同点是WPL值相同
构造1
哈夫曼树可以有不同的构造,例如下面的:
他们的WPL值都相同
W
P
L
值
=
路
径
长
度
∗
权
重
WPL值=路径长度*权重
WPL值=路径长度∗权重都是
23
∗
2
+
11
∗
3
+
5
∗
4
+
3
∗
4
+
2
∗
29
+
3
∗
14
+
4
∗
7
+
8
∗
4
=
271
23*2+11*3+5*4+3*4+2*29+3*14+4*7+8*4=271
23∗2+11∗3+5∗4+3∗4+2∗29+3∗14+4∗7+8∗4=271
仅仅是左右换了下顺序
构造2
这里虽然形状改变了:
但是
路
径
长
度
∗
权
重
路径长度*权重
路径长度∗权重没有改变,依然满足前缀码的性质
,也是哈夫曼树。
5
∗
5
+
29
∗
2
+
7
∗
4
+
8
∗
3
+
14
∗
3
+
23
∗
2
+
3
∗
5
+
11
∗
3
=
271
5*5+29*2+7*4+8*3+14*3+23*2+3*5+11*3=271
5∗5+29∗2+7∗4+8∗3+14∗3+23∗2+3∗5+11∗3=271
代码实现:
函数调用:
#include "HalfManTree.h"
int main()
{
int num = 8;
int weight[num] = {5, 29, 7, 8, 14, 23, 3, 11};
//int weight[num] = {10, 5, 4, 2, 3, 6};
char data[num] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
HalfManItem item[MAX_SIZE];
for (int i = 0; i < MAX_SIZE; i++)
{
item[i].data = 0;
item[i].weight = 0;
item[i].lchild = item[i].rchild = 0;
item[i].used = false;
}
HalfManItem *ht = CreateHalfTable(item, data, weight, num);
//PrintHalfManTable(ht, num);
HalfManNode *tr = CreateHalfManTree(ht, num);
//VisitHalfManTree(tr);
char x = 'a';
std::cout << "\na: ";
GetHalfManCode(tr, x);
std::cout << "\nb: ";
GetHalfManCode(tr, 'b');
std::cout << "\nc: ";
GetHalfManCode(tr, 'c');
std::cout << "\nd: ";
GetHalfManCode(tr, 'd');
std::cout << "\ne: ";
GetHalfManCode(tr, 'e');
std::cout << "\nf: ";
GetHalfManCode(tr, 'f');
std::cout << "\ng: ";
GetHalfManCode(tr, 'g');
std::cout << "\nh: ";
GetHalfManCode(tr, 'h');
//GetHalfManCode(tr,'d');
}
函数定义:
#include <iostream>
#include <stack>
#include <queue>
#include <malloc.h>
#include <string.h>
#define MAX_SIZE 200
#define MAX_INT 10000
//哈夫曼树的构造
typedef struct HalfManNode
{
int freq; //出现频率
char ch; //保存的字符
HalfManNode *lchild, *rchild;
} HalfManNode;
typedef struct HalfManItem
{
char data; //保存字符
bool used; //是否已经加入树了
int weight; //权重
int lchild, rchild; //左孩子、右孩子序号
};
bool Select2Min(HalfManItem *ht, int &nodeNum);
HalfManItem *CreateHalfTable(HalfManItem *item, char data[], int weight[], int &nodeNum);
/***
* 创建哈夫曼树
* 输入 : 数据和权重,总节点数目
*/
HalfManItem *CreateHalfTable(HalfManItem *item, char data[], int weight[], int &nodeNum)
{
for (int i = 0; i < nodeNum; i++)
{
item[i].data = data[i];
item[i].weight = weight[i];
item[i].lchild = item[i].rchild = -1;
item[i].used = false;
}
int parentNum = nodeNum - 1;
//由于有N个节点,每次插入都选择两个根节点权值最小的树作为新节点左右子树,并且创建一个新节点
//根据哈夫曼树的性质,会新建N-1个新节点,一共需要选N-1次
for (int i = nodeNum; i < nodeNum + parentNum; i++)
{
if (Select2Min(item, nodeNum))
{
std::cout << "成功新建节点,左孩子:" << item[nodeNum - 1].lchild << " 右孩子: "
<< item[nodeNum - 1].rchild << " 权重:" << item[nodeNum - 1].weight << std::endl;
}
else
{
return item;
}
}
return item;
}
/**
* 选择哈夫曼树的两个最小的节点
* 输入:
* HalfManTree 树的根节点
* n:节点数量
* *s1\*s2:
*/
bool Select2Min(HalfManItem *ht, int &nodeNum)
{
int min1 = MAX_INT, min2 = MAX_INT;
int minId1 = 0, minId2 = 0;
HalfManItem *p = ht;
int i = 0;
for (; p < ht + nodeNum; p++)
{
if (!p->used && p->weight < min1)
{
min2 = min1;
minId2 = minId1;
min1 = p->weight;
minId1 = i;
} //小于min1,min1更新了,min2也要更新
else if (!p->used && p->weight < min2)
{
min2 = p->weight;
minId2 = i;
}
//比min1大,但是比min2小的情况
i++;
}
if (min2 == MAX_INT)
{
return false;
}
//std::cout << min1<<"|"<<min2<<std::endl;
(ht + nodeNum)->weight = min1 + min2;
(ht + nodeNum)->data = ' ';
(ht + nodeNum)->lchild = minId1;
(ht + nodeNum)->rchild = minId2;
(ht + nodeNum)->used = false;
(ht + minId1)->used = true;
(ht + minId2)->used = true;
nodeNum += 1;
return true;
}
/**
* 打印二叉树全部节点
*/
void PrintHalfManTable(HalfManItem table[], int nodeNum)
{
//std::cout << *table<<" ---- ";
for (int i = 0; i < nodeNum; i++)
{
std::cout << table[i].data << ": 左孩子:" << table[i].lchild << " 右孩子: "
<< table[i].rchild << " 权重:" << table[i].weight << std::endl;
}
return;
}
/***
* 创建哈夫曼树,用一个数组保存一堆哈夫曼节点,最后返回根节点
*/
HalfManNode *CreateHalfManTree(HalfManItem *ht, int nodeNum)
{
HalfManNode *halfTree[nodeNum];
for (int i = 0; i < nodeNum; i++)
{
halfTree[i] = (HalfManNode *)malloc(sizeof(HalfManNode));
std::cout << "访问节点" << ht[i].data << " 权重:" << ht[i].weight << " lchild:"
<< ht[i].lchild << " rchild:" << ht[i].rchild << std::endl;
halfTree[i]->ch = ht[i].data;
halfTree[i]->freq = ht[i].weight;
if (ht[i].lchild != -1)
{
halfTree[i]->lchild = halfTree[ht[i].lchild];
//std::cout << "当前节点:" << halfTree[i]->ch << " "<< "左孩子:" << halfTree[i]->lchild->ch;
}
else
{
halfTree[i]->lchild = NULL;
}
if (ht[i].rchild != -1)
{
halfTree[i]->rchild = halfTree[ht[i].rchild];
//std::cout << "右孩子:" << halfTree[i]->rchild->ch << std::endl;
}
else
{
halfTree[i]->rchild = NULL;
}
//std::cout << "访问节点" << halfTree[i]->ch << " 权重:" << halfTree[i]->freq << " " << std::endl;
}
return halfTree[nodeNum - 1];
}
/***
* 访问一下哈夫曼树
*/
void VisitHalfManTree(HalfManNode *root)
{
if (root)
{
std::cout << "访问节点" << root->ch << " 权重: " << root->freq << " " << std::endl;
VisitHalfManTree(root->lchild);
VisitHalfManTree(root->rchild);
}
}
/***
* 已有哈夫曼树,获得哈夫曼编码
*
*/
void GetHalfManCode(HalfManNode *tr, char ch)
{
std::stack<HalfManNode *> s;
HalfManNode *p = tr, *r = NULL;
while (!s.empty() || p)
{
if (p)
{
s.push(p);
p = p->lchild;
}
else
{
p = s.top();
if (p->rchild && p->rchild != r)
{ //p有右孩子,且没有被访问过
p = p->rchild;
s.push(p);
p = p->lchild;
}
else
{
//std::cout << p->freq << " ";
//std::cout << p->ch<<" "<<ch << " "<<std::endl;
if (ch == p->ch)
{
std::stack<HalfManNode *> s1;
while (!s.empty())
{
s1.push(s.top());
s.pop();
}
while (!s1.empty())
{
p = s1.top();
s1.pop();
if (!s1.empty())
{
if (s1.top() == p->lchild)
{
std::cout << "0";
}
else if (s1.top() == p->rchild)
{
std::cout << "1";
}
else
{
std::cout << "error";
}
}
else
{
return;
}
}
}
s.pop();
r = p;
p = NULL;
}
}
}
}
参考:
其实没怎么参考,感觉网上的都写的好复杂
哈夫曼树的c实现
哈夫曼树的各种语言java,python,c++,c,c#的实现
OBST(最优二叉搜索树)
题目:
已知某系统在通信联络中只可能出现8种字符,其概率分别为0.05,0.29,0.07,0.08,0.14,0.23,0.03,0.11 试着设计赫夫曼编码。
假设权w=(5,29,7,8,14,23,3,11),n=8,则m=15,按照上述算法构造一颗哈夫曼树