0
点赞
收藏
分享

微信扫一扫

哈夫曼树以及应用(哈夫曼编码)概念和建立——附带实现代码以及习题

1 概念

1.1 哈夫曼树

  • 定义:n个带权叶子结点的二叉树中,带权路径长度最小的二叉树。
  • 权:树结点被赋予一个某种一亿的数值;
  • 带权路径长度:从树根结点到任意结点的路径长度(经过的边数)与该结点上权值的乘积。
  • 性质:
  • 1 哈夫曼树可以不唯一,但最小带权路径长度一定是唯一的;
  • 2 哈夫曼树只有度为1的结点;
  • 3 权越小的结点,离根结点越远。

1.2 哈夫曼编码

  • 前缀码:任何一个字符的编码都不是另一个编码的前缀。
  • 前缀码存在意义:不产生混淆,让解码能正常进行。
  • 哈夫曼编码背景:
  • 对于一个给定字符串,有多种前缀码方式,为了信息传递的效率,尽量选择长度最短的编码方式。
  • 把每个字符出现的次数(频率)作为各自叶子结点的权值,那么字符编码成01串的长度为这颗树的带权路径长度。
  • 而带权路径长度最小的树为哈夫曼树。
  • 定义:由哈夫曼树产生的编码方式为哈夫曼编码。

2 实现

2.1 哈夫曼树

  • 构造思想:反复选择两个最小的元素,合并,直到只剩一个元素。
  • 实现方式:堆或优先队列

2.1.2 示例

合并果子
  在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
  例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。
所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。
输入
  输入包括两行,第一行是一个整数n(1<=n<=10000),表示果子的种类数。
  第二行包含n个整数,用空格分隔,第i个整数ai(1<=ai<=20000)是第i种果子的数目。
输出
  输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于2^31。

样例输入 Copy
3
1 2 9
样例输出 Copy
15

  • 方法一(优先队列):

#include 
#include

using std::vector;
using std::priority_queue;
using std::greater;

//小顶堆优先队列
priority_queue<long long, vector<long long>, greater<long long>> q;

int main(int argc, char const *argv[])
{
int n;
long long temp, x, y ,ans = 0;

scanf("%d", &n);

for (int i = 0; i < n; ++i)
{
scanf("%lld", &temp);
q.push(temp);
}

while(q.size() > 1){
x = q.top();
q.pop();

y = q.top();
q.pop();

q.push(x + y);
ans += x + y;
}

printf("%lld\n", ans);
return 0;
}

  • 方法二:小顶堆

#include 
#include

using std::swap;

const int MAXN = 20010;
long long heap[MAXN];
int n;

void downAdjust(int low, int high){
int i = low, j = i * 2;
while(j <= high){
if(j + 1 <= high && heap[j + 1] < heap[j]){
j = j + 1;
}

if(heap[j] < heap[i]){
swap(heap[j], heap[i]);
i = j;
j = i * 2;
}else{
break;
}
}
}

void createHeap(){
for (int i = n / 2; i >= 1; ++i)
{
downAdjust(i, n);
}
}

void deleteTop(){
heap[1] = heap[n--];
downAdjust(1, n);
}

void upAdjust(int low, int high){
int i = high, j = i / 2;
while(j >= low){
if(heap[j] > heap[i]){
swap(heap[j], heap[i]);
i = j;
j = i / 2;
}else{
break;
}
}

}

void Insert(int x){
heap[++n] = x;
upAdjust(1, n);
}

int main(int argc, char const *argv[])
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
scanf("%lld", &heap[i]);
}

createHeap();

long long ans = 0;
long long x, y;
while(n > 1){
x = heap[1];
deleteTop();
y = heap[1];
deleteTop();

Insert(x + y);
ans += x + y;
}

printf("%lld\n", ans);
return 0;
}

2.2 哈夫编码

2.2.1 从叶子到根的逆向顺序构造哈夫曼编码

#include 
#include
#include
#include

using std::swap;
using std::strcpy;

typedef char *HuffmanCode;

const int MAXN = 110;

typedef struct{
int weight;
int lchild, rchild, parent;
}HuffmanNode, *HuffmanTree;

void selectMin(HuffmanTree HT, int n, int &s1, int &s2){
int min = INT_MAX;
for (int i = 1; i <= n; ++i) {
if(HT[i].parent == 0 && min > HT[i].weight){
min = HT[i].weight;
s1 = i;
}
}

min = INT_MAX;
for (int j = 1; j <= n; ++j) {
if(HT[j].parent == 0 && min > HT[j].weight && j != s1){
min = HT[j].weight;
s2 = j;
}
}

if(s1 > s2){
swap(s1, s2);
}

}

void HuffmanCoding(HuffmanTree &HT, HuffmanCode *&HC,int w[],int n){
// w存放n个字符的权值(均>0),构造哈夫曼树HT,并求出n个字符的哈夫曼树编码HC
if(n <= 1) return;
int m = 2 * n -1;//树的所有结点个数
//0号单元未用
HT = new HuffmanNode[m + 1];

//初始化叶子结点
for (int i = 1; i <= n; ++i) {
HT[i].weight = w[i];
HT[i].lchild = HT[i].rchild = HT[i].parent = 0;
}
//初始化非叶子结点
for (int i = n + 1; i<= m; ++i) {
HT[i].lchild = HT[i].rchild = HT[i].parent = 0;
}


for (int i = n + 1; i <= m; ++i)//建立哈夫曼树
{
int s1, s2;
//在HT[1~i-1]中选择parent为0且weight最小的两个结点,其序号分别为s1,s2
selectMin(HT, i - 1, s1, s2);
HT[s1].parent = HT[s2].parent = i;
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;

}
//从叶结点到根逆向求每个字符的哈夫曼编码
HC = new HuffmanCode[n + 1];
//分配n个字符编码的头指针向量([0]不用)
char* cd = new char[n];//分配编码工作空间

cd[n - 1] = '\0';//编码结束符

for (int i = 1; i <= n; ++i)//逐个字符求哈夫曼编码
{
int star = n - 1;//编码结束位置
for (int c = i, f = HT[i].parent;f != 0; c = f, f = HT[f].parent)
{
//从叶结点到根逆向求编码
if(HT[f].lchild == c){
cd[--star] = '0';
}else{
cd[--star] = '1';
}
}
//为第i个字符串编码分配空间,最后一个空间作为结束符
HC[i] = new char[n - star];
//从cd复制编码(串)到HC
strcpy(HC[i], cd + star);
}
delete []cd;//释放工作空间
}

int main(){
HuffmanTree HT;
HuffmanCode *HC;
int n;
int data[MAXN];

while(scanf("%d", &n) != EOF){
for (int i = 1; i <= n; ++i) {
scanf("%d", &data[i]);
}

HuffmanCoding(HT, HC, data, n);

for (int j = 1; j <= n; ++j) {
printf("%s\n", HC[j]);
}

delete(HT);
delete(HC);
}

return 0;
}
/*
哈夫曼的层序遍历为:100,42,58,23,19,29,29,11,8,14,15,5,3,7,8
Input:
8
5 29 7 8 14 23 3 11
Output:
0110
10
1110
1111
110
00
0111
010
*/

2.2.2 根出发直到叶子构造赫夫曼编码

#include 
#include
#include
#include

using std::swap;
using std::strcpy;

typedef char *HuffmanCode;
const int MAXN = 110;

typedef struct
{
int parent, lchild, rchild;
int weight;
}HuffmanNode, *HuffmanTree;

void selectMin(HuffmanTree HT, int n, int &s1, int &s2){
int min = INT_MAX;
for (int i = 1; i <= n; ++i) {
if(HT[i].parent == 0 && min > HT[i].weight){
min = HT[i].weight;
s1 = i;
}
}

min = INT_MAX;
for (int i = 1; i <= n; ++i) {
if(HT[i].parent == 0 && min > HT[i].weight && i != s1){
min = HT[i].weight;
s2 = i;
}
}

if(s1 > s2){
swap(s1, s2);
}
}

//无栈非递归遍历哈夫曼树,求哈夫曼编码
void HuffmanCoding(HuffmanTree &HT, HuffmanCode *&HC, int w[], int n){
if(n <= 1) return;
int m = 2 * n - 1;

HT = new HuffmanNode[m + 1];
//分配n个字符编码的头指针向量[0]不用

for (int i = 1; i <= n; ++i) {
HT[i].weight = w[i];
HT[i].parent = HT[i].lchild = HT[i].rchild = 0;
}

for (int i = n + 1; i <= m; ++i) {
HT[i].parent = HT[i].lchild = HT[i].rchild = 0;
}

//构建哈夫曼树
for (int i = n + 1; i <= m; ++i) {
int s1, s2;
selectMin(HT, i - 1, s1, s2);
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[s1].parent = HT[s2].parent = i;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}

HC = new HuffmanCode[n + 1];
char* cd = new char[n];
int c = m;
int cdlen = 0;
cd[n - 1] = '\0';
for (int i = 1; i <= m; ++i)
{
HT[i].weight = 0;//遍历哈夫曼树作结点状态标志
}

while(c) {
if (HT[c].weight == 0) {//向左
HT[c].weight = 1;
if (HT[c].lchild != 0) {
c = HT[c].lchild;
cd[cdlen++] = '0';
} else if (HT[c].rchild == 0) {//登记叶子结点的编码
HC[c] = new char[cdlen + 1];
cd[cdlen] = '\0';
strcpy(HC[c], cd);//复制编码(串)
}
} else if (HT[c].weight == 1) {//向右
HT[c].weight = 2;
if (HT[c].rchild != 0) {
c = HT[c].rchild;
cd[cdlen++] = '1';
}
} else {//HT[c].weight == 2, 退回
HT[c].weight = 0;
c = HT[c].parent;
--cdlen;//退回父结点,编码长度减一
}

}
delete []cd;
}




int main(int argc, char const *argv[])
{
HuffmanTree HT;
HuffmanCode *HC;
int n;
int data[MAXN];

while(scanf("%d", &n) != EOF){
for (int i = 1; i <= n; ++i) {
scanf("%d", &data[i]);
}

HuffmanCoding(HT, HC, data, n);
for (int i = 1; i <= n; ++i) {
printf("%s\n", HC[i]);
}
delete(HC);
delete(HT);
}

return 0;
}
/*
Input:
8
5 29 7 8 14 23 3 11
Output:
0110
10
1110
1111
110
00
0111
010
*/

举报

相关推荐

0 条评论