贪心算法-霍夫曼编码

霍夫曼编码是一种无损数据压缩算法。在计算机数据处理中,霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用较长的编码,这便使编码之后的字符串的平均长度、期望值降低,从而达到无损压缩数据的目的。例如,在英文中,e的出现机率最高,而z的出现概率则最低。当利用霍夫曼编码对一篇英文进行压缩时,e极有可能用一个比特来表示,而z则可能花去25个比特(不是26)。用普通的表示方法时,每个英文字母均占用一个字节(byte),即8个比特。二者相比,e使用了一般编码的1/8的长度,z则使用了3倍多。倘若我们能实现对于英文中各个字母出现概率的较准确的估算,就可以大幅度提高无损压缩的比例。

构建霍夫曼编码主要包括两个部分:

1)根据输入的字符串构建霍夫曼树。

2)便利霍夫曼数并给每个字符分配编码。

哈夫曼树(Huffman Tree),又叫最优二叉树,指的是对于一组具有确定权值的叶子结点的具有最小带权路径长度的二叉树。

(1)路劲(Path):从树中的一个结点到另一个结点之间的分支构成两个结点间的路径。

(2)路径长度(Path Length):路径上的分支树。

(3)树的路径长度(Path Length of Tree):从树的根结点到每个结点的路径长度之和。在结点数目相同的二叉树中,完全二叉树的路径长度最短。

(4)结点的权(Weight of  Node):在一些应用中,赋予树中结点的一个有实际意义的树。

(5)结点的带权路径长度(Weight Path Length of Node):从该结点到树的根结点的路径长度与该结点的权的乘积。

(6)树的带权路径长度(WPL):树中所有叶子结点的带权路径长度之和

 

构建霍夫曼树的步骤:

算法:输入是没有相同元素的字符数组(长度n)以及字符出现的频率,输出是哈夫曼树。

即假设有n个字符,则构造出得哈夫曼树有n个叶子结点。n个字符的权值(频率)分别设为w1,w2,…,wn,则哈夫曼树的构造规则为:

(1)将w1,w2,…,wn看成是有n棵树的森林(每棵树仅有一个结点);

(2)在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;

(3)从森林中删除选取的两棵树,并将新树加入森林;

(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

用一个例子来了解该算法:

character   Frequency
    a            5
    b           9
    c           12
    d           13
    e           16
    f           45

第1步:将每个元素构造成一个节点,即只有一个元素的树。并构建一个最小堆,包含所有的节点,该算法用了最小堆来作为优先队列。

第2步:选取两个权值最小的节点,并添加一个权值为5+9=14的节点,作为他们的父节点。并更新最小堆,现在最小堆包含5个节点,其中4个树还是原来的节点,权值为5和9的节点合并为一个。

character           Frequency
       c               12
       d               13
     内部 节点           14
       e               16
       f               45

重复上面的步骤,直到最小堆只有一个节点。

character      Frequency
 内部节点         100

C语言实现如下:

#include <stdio.h>
#include <stdlib.h>

#define MAX_TREE_HT 100

// 一个霍夫曼树节点
struct MinHeapNode
{
    char data;  // 输入的字符数组中的一个字符
    unsigned freq;  // 字符出现的次数
    struct MinHeapNode *left, *right;
};

// 最小堆: 作为优先队列使用
struct MinHeap
{
    unsigned size;    // 最小堆元素的个数
    unsigned capacity;   //最大容量
    struct MinHeapNode **array;
};

//初始化一个最小堆节点
struct MinHeapNode* newNode(char data, unsigned freq)
{
    struct MinHeapNode* temp =
          (struct MinHeapNode*) malloc(sizeof(struct MinHeapNode));
    temp->left = temp->right = NULL;
    temp->data = data;
    temp->freq = freq;
    return temp;
}

// 创建一个容量为capacity 的最小堆
struct MinHeap* createMinHeap(unsigned capacity)
{
    struct MinHeap* minHeap =
         (struct MinHeap*) malloc(sizeof(struct MinHeap));
    minHeap->size = 0;  // current size is 0
    minHeap->capacity = capacity;
    minHeap->array =
     (struct MinHeapNode**)malloc(minHeap->capacity * sizeof(struct MinHeapNode*));
    return minHeap;
}

//  swap 两个堆节点
void swapMinHeapNode(struct MinHeapNode** a, struct MinHeapNode** b)
{
    struct MinHeapNode* t = *a;
    *a = *b;
    *b = t;
}

// 更新最小堆.
void minHeapify(struct MinHeap* minHeap, int idx)
{
    int smallest = idx;
    int left = 2 * idx + 1;
    int right = 2 * idx + 2;

    if (left < minHeap->size &&
        minHeap->array[left]->freq < minHeap->array[smallest]->freq)
      smallest = left;

    if (right < minHeap->size &&
        minHeap->array[right]->freq < minHeap->array[smallest]->freq)
      smallest = right;

    if (smallest != idx)
    {
        swapMinHeapNode(&minHeap->array[smallest], &minHeap->array[idx]);
        minHeapify(minHeap, smallest);
    }
}

//检测堆的大小是否为1
int isSizeOne(struct MinHeap* minHeap)
{
    return (minHeap->size == 1);
}

//取得堆中最小的节点
struct MinHeapNode* extractMin(struct MinHeap* minHeap)
{
    struct MinHeapNode* temp = minHeap->array[0];
    minHeap->array[0] = minHeap->array[minHeap->size - 1];
    --minHeap->size;
    minHeapify(minHeap, 0);
    return temp;
}

// 想最小堆中插入一个节点
void insertMinHeap(struct MinHeap* minHeap, struct MinHeapNode* minHeapNode)
{
    ++minHeap->size;
    int i = minHeap->size - 1;
    while (i && minHeapNode->freq < minHeap->array[(i - 1)/2]->freq)
    {
        minHeap->array[i] = minHeap->array[(i - 1)/2];
        i = (i - 1)/2;
    }
    minHeap->array[i] = minHeapNode;
}

//构建一个最小堆
void buildMinHeap(struct MinHeap* minHeap)
{
    int n = minHeap->size - 1;
    int i;
    for (i = (n - 1) / 2; i >= 0; --i)
        minHeapify(minHeap, i);
}

void printArr(int arr[], int n)
{
    int i;
    for (i = 0; i < n; ++i)
        printf("%d", arr[i]);
    printf("\n");
}

// 检测是否是叶子节点
int isLeaf(struct MinHeapNode* root)
{
    return !(root->left) && !(root->right) ;
}

// 创建一个容量为 size的最小堆,并插入 data[] 中的元素到最小堆
struct MinHeap* createAndBuildMinHeap(char data[], int freq[], int size)
{
    struct MinHeap* minHeap = createMinHeap(size);
    for (int i = 0; i < size; ++i)
        minHeap->array[i] = newNode(data[i], freq[i]);
    minHeap->size = size;
    buildMinHeap(minHeap);
    return minHeap;
}

// 构建霍夫曼树
struct MinHeapNode* buildHuffmanTree(char data[], int freq[], int size)
{
    struct MinHeapNode *left, *right, *top;

    // 第 1步 : 创建最小堆.
    struct MinHeap* minHeap = createAndBuildMinHeap(data, freq, size);

    //知道最小堆只有一个元素
    while (!isSizeOne(minHeap))
    {
        // 第二步: 取到最小的两个元素
        left = extractMin(minHeap);
        right = extractMin(minHeap);

        // Step 3: 根据两个最小的节点,来创建一个新的内部节点
        // '$' 只是对内部节点的一个特殊标记,没有使用
        top = newNode('$', left->freq + right->freq);
        top->left = left;
        top->right = right;
        insertMinHeap(minHeap, top);
    }

    // 第4步: 最后剩下的一个节点即为跟节点
    return extractMin(minHeap);
}

// 打印霍夫曼编码
void printCodes(struct MinHeapNode* root, int arr[], int top)
{
    if (root->left)
    {
        arr[top] = 0;
        printCodes(root->left, arr, top + 1);
    }

    if (root->right)
    {
        arr[top] = 1;
        printCodes(root->right, arr, top + 1);
    }

    // 如果是叶子节点就打印
    if (isLeaf(root))
    {
        printf("%c: ", root->data);
        printArr(arr, top);
    }
}

// 构建霍夫曼树,并遍历打印该霍夫曼树
void HuffmanCodes(char data[], int freq[], int size)
{
   //  构建霍夫曼树
   struct MinHeapNode* root = buildHuffmanTree(data, freq, size);

   // 打印构建好的霍夫曼树
   int arr[MAX_TREE_HT], top = 0;
   printCodes(root, arr, top);
}

// 测试
int main()
{
    char arr[] = {'a', 'b', 'c', 'd', 'e', 'f'};
    int freq[] = {5, 9, 12, 13, 16, 45};
    int size = sizeof(arr)/sizeof(arr[0]);
    HuffmanCodes(arr, freq, size);
    return 0;
}

运行结果如下:

f: 0
c: 100
d: 101
a: 1100
b: 1101
e: 111

时间复杂度

O(nlogn), 其中n是字符的数量。extractMin() 调用了 2*(n-1)次,extractMin()为log(n)的复杂度。

时间: 2024-09-11 07:46:46

贪心算法-霍夫曼编码的相关文章

范式霍夫曼编码 码长不连续 产生不能识别部分编码

问题描述 范式霍夫曼编码 码长不连续 产生不能识别部分编码 如图 范式霍夫曼编码 遇到这种 编码长度不连续的情况 网上说 extern KBitInputStream bs; int len = 1; int code = bs.ReadBit(); while(code >= first[len]) { code <<= 1; code |= (bs.ReadBit()); // append next input bit to code len++; } len--; // 至此,识

编码-霍夫曼树程序,输入字符串统计字符出现次数并译码。请问如何改成从文件读入字符串?

问题描述 霍夫曼树程序,输入字符串统计字符出现次数并译码.请问如何改成从文件读入字符串? //生成HuffmanCode文件的两个函数void HuffmanEncoding(HuffmanTree HTHuffmanCode HC){//根据HuffmanTreeHT求HuffmanCode表HC int cpi; char cd[n]; int start; cd[num] = ''; for(i = 1;i <= num;i++){ start = num; c = i; while((p

【OJ】贪心法 Fence Repair POJ 3253 霍夫曼(Huffman)编码原理 acmclub 12326

题目链接:点击打开链接 /* 贪心法 Fence Repair POJ 3253 霍夫曼(Huffman)编码原理 */ #include<iostream> #include<algorithm> typedef long long LL; using namespace std; int l[50010]; int main(){ int j,m=0,n;cin>>n; LL s=0,ans=0; for(int i=0;i<n;i++)cin>>

哈夫曼编码问题 语言-求大神更正一下哈夫曼编码,重谢!

问题描述 求大神更正一下哈夫曼编码,重谢! #include #include #include static float weights[]={12.702 ,9.056 ,8.167 ,7.507 ,6.966 ,6.749 ,6.327 , 6.094 ,5.987 ,4.253 ,4.025 ,2.782 ,2.758 ,2.406 , 2.360 ,2.228 ,2.105 ,1.974 ,1.929 ,1.492 ,0.978 , 0.722 ,0.153 ,0.150 ,0.095

哈夫曼(huffman)树和哈夫曼编码

哈夫曼树 哈夫曼树也叫最优二叉树(哈夫曼树)    问题:什么是哈夫曼树? 例:将学生的百分制成绩转换为五分制成绩:≥90 分: A,80-89分: B,70-79分: C,60-69分: D,<60分: E. if (a < 60){ b = 'E'; } else if (a < 70) { b = 'D'; } else if (a<80) { b = 'C'; } else if (a<90){ b = 'B'; } else { b = 'A'; } 判别树:用于描

电工4课程作业2:哈夫曼编码实验

问题描述 电工4课程作业2:哈夫曼编码实验 1) 问题描述设某编码系统共有n个字符,使用频率分别为{w1, w2, -, wn},设计一个不等长的编码方案,使得该编码系统的空间效率最好.2) 基本要求(1) 设计数据结构:(2) 设计编码算法:(3) 分析时间复杂度和空间复杂度. 解决方案 http://blog.csdn.net/fduan/article/details/7837444http://blog.sina.com.cn/s/blog_686d0fb001012qmh.html 解

哈夫曼树及哈夫曼编码

         一,引言          如上图,是一个判断体重在什么范围内的判定树,例如,学校体检的时候,我们反复用这个算法,当你输入一个体重:200斤,然后程序就开始反复判断了,经过三次判断,它发现你过重,然后重启系统了,又来一个人,还是200斤,三次判断之后,又系统重启了-后面的200多个200多斤的盘子判断完了之后,来了个120的,终于是个比较正常的体重了,但是系统一判断完,系统还是重启,反复检查之后,发现你那台8086时代的电脑终于撑不住了~      于是你改了下算法,换了一棵判

请问霍夫曼压缩图片时图片应该转换为什么格式?

问题描述 本人小白,目前想用C#写一个图片压缩的程序,用霍夫曼算法的时候出现了一点问题,我不知道应该把图片转为为什么格式来作为霍夫曼算法的输入,查网上资料也没个准,不知道是string.byte数组还是什么别的.求大家点拨. 解决方案 解决方案二:byte[]或者Stream.解决方案三:引用1楼sp1234的回复: byte[]或者Stream. Base64String格式可以吗?解决方案四:不可以!虽然你可以在传递时使用Base64String,但运算时还是要解码的解决方案五:引用3楼xu

关于哈夫曼编码的程序运行时出错,我分析是由于cd定义出现了问题,导致后边cd[--start]出错

问题描述 关于哈夫曼编码的程序运行时出错,我分析是由于cd定义出现了问题,导致后边cd[--start]出错 void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n){ //w存放n个字符的权值(均>0),构造赫夫曼树 HT,并求出n个字符的赫夫曼编码 HC printf("123"); system("pause"); int s1,s2,i,start; int f=0