赫夫曼编码(优先队列实现)

/*

Name: 赫夫曼编码(优先队列实现)

Copyright:

Author: 巧若拙

Date: 28/09/14 12:17

Description:

采用优先队列把一个普通线性表改造成赫夫曼树,再进行赫夫曼编码,得到一个同时记录了明文和对应编码的密码本。

使用优先队列(最小堆)构造赫夫曼树是一种高效的方法,比每次都遍历整个线性表要快很多。

我在构造密码本时确保密码本数组递增排序,这样每次插入新结点时可以折半查找插入,效率较高。

有序的密码本在把明文编码成密文时也可以大大提高查找效率。

*/

#include<stdio.h>

#include<stdlib.h>

#include<malloc.h>

#include<math.h>

#include<time.h>

#define MAXSIZE 300

#define OK 1

#define ERROR 0

#define TRUE 1

#define FALSE 0

typedef char ElemType;

typedef int Status; //函数类型,其值是函数结果状态代码,如OK等

typedef struct {

ElemType data;

int weight;

int parent;

int lchild;

int rchild;

} HNodeType;    //结点结构体

typedef struct {

ElemType data; //明文信息

char *code;    //密文信息

} HCodeType;    //编码结构体

void BuildMinHeap(HNodeType Hf[], int n);//将线性表Hf改造成一个最小堆

void MinHeapSiftDown(HNodeType Hf[], int n, int pos);//向下调整二叉堆的第pos个元素,使其满足最小堆的特征

void HuffmanTree (HNodeType Hf[], int n);//将线性表改造成赫夫曼树

void DisplayCodebook(HCodeType Cb[], int n);//显示密码本中的明码和对应密码信息

int InsertCodeLib(HCodeType Cb[], int n, ElemType x, char *s, int len);//将新结点插入到密码本的编码线性表中

int HfCoding(HCodeType Cb[], HNodeType Hf[], int n);//根据赫夫曼树进行赫夫曼编码,生成密码本

int main(void)

{

HNodeType Hf[MAXSIZE*2];   //赫夫曼树

HCodeType codebook[MAXSIZE]; //密码本

int lenHf = 5; //赫夫曼树叶子结点个数

int lenCb;    //密码本长度(等于lenHf)

int i;

for (i=0; i<lenHf; i++) //随机产生数据和权值

{

Hf[i].data = ‘A‘ + i;

Hf[i].weight = rand() % 10 + 1;

printf("%c : %d\n", Hf[i].data, Hf[i].weight);

}

HuffmanTree (Hf, lenHf); //将线性表改造成赫夫曼树

lenCb = HfCoding(codebook, Hf, lenHf*2-1);//根据赫夫曼树进行赫夫曼编码,生成密码本

puts("密码本:");

DisplayCodebook(codebook, lenCb);//显示密码本中的明码和对应密码信息

return 0;

}

/*

函数功能:将线性表改造成赫夫曼树

初始条件:非递减线性表Hf已经存在

操作结果:先将原线性表改造成最小堆(优先队列),然后按照如下顺序处理该最小堆:

删除权重最小的两个结点,并将他们加入到队列尾部,将该两个最小结点的权值相加,生成一个新结点,并将新结点加入到优先队列。

不断调整该最小堆,直到只剩下一个结点,即得到赫夫曼树。

注意:每次产生新结点时,只需设置新结点的权重及孩子结点信息,双亲结点信息等赫夫曼树构造完毕后再统一计算。

*/

void HuffmanTree (HNodeType Hf[], int n)

{

int i;

int len = 2*n - 1;

int front = n -1;

int rear = len - 1;

BuildMinHeap(Hf, n);

for (i=0; i<len; i++) //初始化孩子结点位置

{

Hf[i].lchild = Hf[i].rchild = -1;

}

while (front > 0)

{

//删除权重最小的两个结点,并将他们加入到队列尾部

Hf[rear] = Hf[0];

Hf[0] = Hf[front];

MinHeapSiftDown(Hf, front+1, 1);//向下调整二叉堆的第1个元素,使其满足最小堆的特征

Hf[rear-1] = Hf[0];

//设置新结点的权重及孩子结点信息

Hf[0].weight = Hf[rear].weight + Hf[rear-1].weight;

Hf[0].lchild = rear;

Hf[0].rchild = rear - 1;

MinHeapSiftDown(Hf, front, 1);//向下调整二叉堆的第1个元素,使其满足最小堆的特征

rear -= 2; //由于有两个结点加入新队列,队尾指示前移2位

front--;//由于有(2-1 = 1)结点出列,队头指示前移1位

}

//计算父亲结点位置

Hf[0].parent = -1;

for (i=0; i<len; i++)

{

Hf[Hf[i].lchild].parent = Hf[Hf[i].rchild].parent = i;

}

}

/*

函数功能:将新结点插入到密码本的编码线性表中

初始条件:每个编码的明文和密文信息分别记录在x和s中,len是字符串s的长度

操作结果:先折半查找插入位置,然后插入新结点,将x和s的信息复制给新结点,注意s中存储的结点编码的路径是从叶子到根,存储到密码本时需要逆序复制 。

*/

int InsertCodeLib(HCodeType Cb[], int n, ElemType x, char *s, int len)//采用二分查找插入排序

{

int i, mid;

int left = 0, right = n-1;

while (left <= right)//折半查找插入位置

{

mid = (left + right) / 2;

if (x > Cb[mid].data)

{

left = mid + 1;

}

else

{

right = mid -1;

}

}

//新数据,执行插入操作

for (i=n; i>left; i--)

{

Cb[i] = Cb[i-1];

}

Cb[left].code = (char*)malloc(sizeof(char)*(len+1));

if (!Cb[left].code)

{

printf("Out of space!");

exit(0);

}

for (i=len-1; i>=0; i--) //把s的字符串逆序复制到code

{

Cb[left].code[len-1-i] = s[i];

}

Cb[left].code[len] = ‘\0‘;

Cb[left].data = x;

return n+1;

}

/*

函数功能:根据赫夫曼树进行赫夫曼编码,生成密码本

初始条件:赫夫曼树Hf已经存在,n是赫夫曼树的长度

操作结果:对赫夫曼树的叶子结点进行编码,并存储到密码本,返回密码本长度。

对密码本中的各个结点采用二分查找插入,构造非递减序列,以便于查找和编码。

*/

int HfCoding(HCodeType Cb[], HNodeType Hf[], int n)

{

int i, j, k, f;

int top = 0; //为编码结构体构造一个栈,top指示栈顶

char str[MAXSIZE]; //存储编码的临时字符串

for (i=0; i<n; i++)

{

if (Hf[i].lchild == -1)//是叶子结点

{

j = i;

f = Hf[j].parent;

k = 0;

while (f != -1) //逆序存储该结点的编码,注意结点编码的路径是从叶子到根,存储到密码本时需要逆序复制

{

if (j == Hf[f].lchild)

{

str[k++] = ‘0‘;

}

else

{

str[k++] = ‘1‘;

}

j = f;

f = Hf[j].parent;

}

str[k] = ‘\0‘;

top = InsertCodeLib(Cb, top, Hf[i].data, str, k);//采用二分查找插入排序

}

}

return top;

}

/*

函数功能:显示密码本中的明码和对应密码信息

初始条件:密码本已经存在

操作结果:显示密码本中的明码和对应密码信息。

*/

void DisplayCodebook(HCodeType Cb[], int n)

{

int i, j;

for (i=0; i<n; i++)

{

printf("%c: ", Cb[i].data);

puts(Cb[i].code);

}

}

/*

函数功能:向下调整二叉堆的第pos个元素,使其满足最小堆的特征

初始条件:最小堆Hf已经存在,只有第pos个元素不满足特征

操作结果:向下调整二叉堆的第pos个元素,使其满足最小堆的特征。

*/

void MinHeapSiftDown(HNodeType Hf[], int n, int pos)

{

HNodeType temp = Hf[pos-1];

int child = pos * 2; //指向左孩子

while (child <= n)

{

if (child < n && Hf[child-1].weight > Hf[child].weight) //有右孩子,且右孩子更小些,定位其右孩子

child += 1;

if (Hf[child-1].weight < temp.weight) //通过向上移动孩子结点值的方式,确保双亲大于孩子

{

Hf[pos-1] = Hf[child-1];

pos = child;

child = pos * 2;

}

else

break;

}

Hf[pos-1] = temp; //将temp向下调整到适当位置

}

/*

函数功能:将线性表Hf改造成一个最小堆

初始条件:线性表Hf已经存在

操作结果:将线性表Hf改造成一个最小堆

*/

void BuildMinHeap(HNodeType Hf[], int n)

{

int i;

for (i=n/2; i>0; i--)

{

MinHeapSiftDown(Hf, n, i);

}

}

时间: 2024-10-18 11:13:21

赫夫曼编码(优先队列实现)的相关文章

使用优先队列构建赫夫曼树

关于赫夫曼编码和赫夫曼树的相关知识可参考之前两篇文章(由二叉树构造赫夫曼树.赫夫曼编码).本文介绍另一种构建赫夫曼树的方式,采用优先队列. 步骤: 1.首先我们需要统计不同字符出现的次数.一个字符出现的次数越多,说明其优先级越高,其赫夫曼编码应该越短: 2.将待编码的字符(即带权节点)存入优先级队列,优先级即字符出现的次数: 3.不断迭代队列,直到队列中剩下一个元素(即根节点).每次从队列中取出两个优先级最小的元素(优先级队列中的元素是按照节点优先级顺序排列的),然后生成一个新的赫夫曼树节点,节

【算法导论】贪心算法之赫夫曼编码

概述 讨论赫夫曼编码问题,赫夫曼编码的思想就是变长编码.变长编码就是让字符表中出现概率高的字符的编码长度尽可能小,而出现概率高的字符的编码长度相对较长.然后还要遵循前缀码的要求,就是任意一个编码都不是其他编码的前缀码,这样方便解码. 思路及实现 对于下表中的字符和相应的出现概率,有对应图中的编码树: 可以比较容易的看出来,每个叶节点就代表一个字符,从根节点到叶节点走过的路径拼接起来,就代表这个字符的编码,比如f是1100,e是1101,而f和e是深度最深的节点也是概率最小的两个节点.这也就是我们

贪心算法之赫夫曼编码

贪心算法之赫夫曼编码 编码基本介绍 等长编码 变长编码 前缀码 赫夫曼编码的构造 贪心选择是安全的 最优子结构 编码实现 编码树节点TreeNode 优先队列的实现 赫夫曼编码的构建 maincc和Makefile 编译运行 贪心算法之赫夫曼编码 赫夫曼编码(Huffman coding)是一种编码方式,赫夫曼编码是变长编码的一种.可以有效的压缩数据,一般可以节约20%~90%的空间,这一般是由文件的数据特性决定的! 编码基本介绍 一般来说吗,文件可以分为两种:文本文件,二进制文件.这种区分只是

51nod1428(优先队列)

题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1428 题意:中文题诶- 思路:贪心 问最少要多少教室就是求最多有多少个时间段产生了交集咯.我们先用结构体存储区间并将其按照左端点升序排列,若左端点相同则按右端点升序排列. 接下来遍历所有区间,并维护一个优先队列,其中存储区间右端点值.对于当前区间,我们将优先队列中所有比当前区间左端点小的元素删除(因为其所在区间不会与当前区间相交嘛),然后再将当前区间的右端点加

优先队列实现哈弗曼最小权值

建立哈弗曼树要求我们每次都选频率权值最小的点构成节点,即权值小的点在树的深处,权值大的点在树的浅处,根据节点选择的特点,我们可以把节点的值放在优先队列中,包括新形成的节点. 我们先定义优先队列的优先级别. 1 struct cmp 2 { 3 bool operator()(const int &a,const int &b) 4 { 5 return a>b; 6 } 7 };//最小值优先出队 然后就是实现的整个程序. #include<stdio.h> #inclu

NYOJ 284 坦克大战 &amp;&amp; POJ 2312 Battle City (广搜+优先队列)

链接:click here~~ 题意: 描述 Many of us had played the game "Battle city" in our childhood, and some people (like me) even often play it on computer now. What we are discussing is a simple edition of this game. Given a map that consists of empty space

hdu 4006 The kth great number(优先队列)

The kth great number Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65768/65768 K (Java/Others) Total Submission(s): 6982    Accepted Submission(s): 2837 Problem Description Xiao Ming and Xiao Bao are playing a simple Numbers game. In a roun

优先队列(堆)

一.优先队列的一些简单的实现: 1. 使用一个简单的链表在表头以O(1) 执行插入操作,并遍历该链表以删除最小元,这需要O(N) 的时间. 2. 始终让表保持有序状态:这使得插入代价高昂(O(N)), 而删除代价低廉(O(1)).基于删除最小元的操作从不多于插入操作的事实,因此,前者是更好地想法. 3. 使用二叉查找树,它对这两种操作的平均运行时间是O(logN).尽管插入是随机的,而删除不是,但这个结论还是成立的.由于删除的唯一元素是最小元.反复除去左子树中的节点似乎损害树的平衡,使得右子树加

优先队列比较符重载

#include <iostream> #include <queue> using namespace std; struct Node{ int x, y; friend bool operator<(Node a, Node b){ return a.x > b.x; //x最小的节点在队首 } }; int main(){ priority_queue<Node> PQ; Node temp = {2, 3}; PQ.push(temp); temp