精华内容
下载资源
问答
  • 最优前缀编码 哈夫曼算法
    2021-06-14 17:33:54

    1. 问题

    • 给定字符集 C = < x 1 , x 2 , . . . , x n > C=<x_1,x_2,...,x_n> C=<x1,x2,...,xn> 和每个字符的频率 f ( x i ) f(x_i) f(xi),求关于 C C C 的一个最优前缀码。

    2. 解析

    • 构造最优前缀码的贪心算法就是哈夫曼算法

    • p o p : pop: pop: 每次从队列中取出两个权值最小的节点当作孩子节点

    • p u s h : push: push 根据 p o p pop pop 操作取出的两个子结点,构成一个带有新的权值(两个子结点权值相加)的父结点后重新放入队列。

    • 重复 n − 1 n-1 n1 p o p pop pop p u s h push push 操作后,就构成了一颗哈夫曼树。

    • 这里设定右节点的边值为1,左节点的边值为0,那么从祖先节点到某个叶子节点的边集集合就是其最优前缀编码。
      在这里插入图片描述
      在这里插入图片描述

    3. 设计

    最优前缀编码问题(哈夫曼算法)

    #include<bits/stdc++.h>
    const int N = 1e3+10;
    using namespace std;
    
    
    
    struct node {
    	int val,id;
    	bool friend operator<(const node &x,const node &y) {
    		//按照键值降序排序
    		if (x.val!=y.val) return x.val>y.val;
    		return x.id>y.id;
    	}
    };
    
    struct tree {
    	int l,r,val;
    }t[N];
    
    string s;
    priority_queue<node>q;
    int n,m,k;
    int fa[N];
    string HafCode[N];
    
    /**
     * 从叶子节点开始跳到祖先节点
     */
    void getHafCode() {
    	for (int i=1;i<=n;i++) {
    		string c;
    		int j = i;
    		while (fa[j]) {
    			c = (j==t[fa[j]].l?'0':'1')+c;
    			j = fa[j];
    		}
    		HafCode[i] = c;
    	}
    }
    
    void Haf() {
    	/**
    	 * 哈夫曼算法最多执行n-1次
    	 */
    	for (int i=1;i<n;i++) {
    		//每次从堆中取出2个值最小的节点
    		node a = q.top();
    		q.pop();
    		node b = q.top();
    		q.pop();
    
    		int id = i+n;
    		int val = a.val+b.val;
    
    		//建立树结构
    		t[id].l = a.id;
    		t[id].r = b.id;
    		t[id].val = val;
    		fa[a.id] = id;
    		fa[b.id] = id;
    		q.push({val,id});
    	}
    	getHafCode();
    }
    
    void run() {
    	cin>>s;
    	n = s.length();
    	for (int i=1;i<=n;i++) {
    		int val;
    		cin>>val;
    		q.push({val,i});
    	}
    	Haf();
    	for (int i=1;i<=n;i++) {
    		cout<<s[i-1]<<":";
    		cout<<HafCode[i]<<endl;
    	}
    }
    
    int main() {
    	run();
    	return 0;
    }
    

    4. 分析

    • 时间复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)

    5. 源码

    https://github.com/a894985555/Algorithm/tree/main/%E6%9C%80%E4%BC%98%E5%89%8D%E7%BC%80%E7%BC%96%E7%A0%81

    更多相关内容
  • 哈夫曼算法

    2018-03-09 12:41:06
    使用哈夫曼算法对字符进行排序,通过字符出现的权值求出哈夫曼树,并给出哈夫曼编码
  • 哈夫曼算法在图像压缩中的应用。。。。。。。
  • 哈夫曼编码的方法,实现对字符文本的压缩,并且能够还原源文件。两个程序,一个是编码程序,一个是解码程序。
  • word教育资料 设计一个利用哈夫曼算法的编码系统重复地显示并处理以下项目直到选择退出为止 基本要求 1? 将权值数据存放在数据文件(文件名为data.txt位于执行程序的当前目录中) 2? 初始化键盘输入字符集大小nn个...
  • 霍夫曼编码及其改进 在通信中为了提高信息传输效率得到或接近信息熵的...哈夫曼编码(Huffman Coding)是一种无失真编码方式是可变字长编码(VLC)的一种由 Huffman 于 1952 年提出该方法完全依据字符出现概率来构造异字头
  • 这是学校数据结构与算法设计课程的PJ,旨在实现类似zip软件的压缩与解压功能。我在几乎有空就在写代码的情况下两周完成了这个项目。 目前网上能够搜索到的资料对于单个文件和文本文件的压缩与解压较多,而对文件夹与...
  • 利用C++实现哈夫曼算法,算法简单易懂,在vs2010上运行
  • 霍夫曼编码及其改进 在通信中为了提高信息传输效率得到或接近信息熵的...哈夫曼编码(Huffman Coding)是一种无失真编码方式是可变字长编码(VLC)的一种由 Huffman 于1952 年提出该方法完全依据字符出现概率来构造异字头
  • 数据结构课程哈夫曼算法的c语言实现。二叉树应用。vs2013
  • 根据每个字符的出现频率,哈夫曼贪心算法构造出字符最优的二进制表示。 假定我们希望压缩一个十万个字符的数据文件,设文中只有6个不同字符,每个字符的频次、定长编码、变长编码如下表所示: 信息 a b c d e...

    一、哈夫曼编码

    1. 哈夫曼编码简介
      哈夫曼编码可以有效地压缩数据,通常可以节省20%~90%的空间,具体压缩率依赖于数据的特性。我们将待压缩数据看作字符序列。根据每个字符的出现频率,哈夫曼贪心算法构造出字符最优的二进制表示。
    • 假定我们希望压缩一个十万个字符的数据文件,设文中只有6个不同字符,每个字符的频次、定长编码、变长编码如下表所示:
    信息abcdef
    频次(千次)4513121695
    定长编码000001010011100101
    导管010110011111011100
    • 说明:以上这么多个字符的文件,只包含1~6个不同字符。如果用传统的定长编码来表示一个字符,一个字符用三位二进制表示,则文件编码长度是30万位,如用变长编码表示,则仅需224000位。
    • 变长编码可以达到比定长编码好得多的压缩率,其思想是赋予高频字符短码字,低频字符长码字。实际上,这种编码方法是此文件的最优字符编码,也就是哈夫曼编码。
    1. 前缀码
      所谓前缀码就是没有任何码字是其他码字的前缀。比如,a的码字是0,而0不是任何b~f一个字母对应码字的前缀(从上表可以看出其他码字的第一位均为1);b的码字是101,而101也不是任何其他字母对应码字的前缀。可以证明前缀码确实可以保证达到最优数据压缩率。
      任何二进制的字符吗的编码过程很简单,只要将表示每个字符的码字链接起来即可完成文件压缩。比如,采用上表的变长前缀码,我们可以将三个字符的文件abc编码成0+101+100=0101100
      前缀码还有一个作用就是可以简化解码的过程。由于没有码字是其他码字的前缀,编码文件的开始码字是是无歧义的。我们可以简单地识别出开始码字,将其转为原字符,然后对编码文件其余部分重复这种解码过程。
    • 前缀码解码方式举例:我们现在要根据上表解码0101100.首先扫描到字符0,对应a,现在不用继续往后扫描了,因为根据前缀码的无歧义性质,不可能有01,00,0101等码字有对应的字符了。现在解101100,扫描到1,没有对应字符,继续扫描到10,也没有,继续扫描到101,对应b,停止本次扫描;下一轮扫描到100,对应c.所以这种二进制串可以唯一对应一串字符。

    二、哈夫曼树

    1. 概念引入
      解码的过程需要前缀码的一种方便的表示形式,以便我们可以很容易地截取开始码字。一种二叉树可以满足这种需求,其叶子结点为给定的字符,字符的二进制码字用从根节点到该字符的叶子结点的简单路径表示,其中0意味着转向左孩子1意味着转向右孩子。下图给出了两种编码方法的二叉树表示:
      2种编码方法的二叉树表示
    • 举例:在定长编码方法中,看二叉树,从根节点到c,先转向左孩子,再转向右孩子,再转向左孩子,对应编码010.以此类推。
      可以看到,文件的最优编码方案总是对应一颗满二叉树,即这个二叉树的结点要么为叶子结点,要么有两个孩子。所以我们的重点就放在这个满二叉树上。设C为字母表且所有字符的出现频率均为正数,则最优前缀码对应的数恰好有|C|个叶子结点,每个叶子结点对应字母表中的一个字符,而且恰好有|C|-1个内部结点。这种满二叉树就是哈夫曼树
    1. 计算哈夫曼树T编码一个文件所需的二进制位数
      对字母表C中的每个字符c,令属性c.freq表示c在文件中出现的频率,令dT©表示c的叶子结点在树中的深度,也是字符c的码字长度。则编码文件需要的二进制位数B(T)为
      B ( T ) = ∑ c ∈ C c . f r e q ⋅ d T ( c ) B(T) = \sum\limits_{c\in C}c.freq·d_T(c) B(T)=cCc.freqdT(c)
      这里我们将B(T)定义为T的代价。

    三、哈夫曼算法

    1. 算法描述
      哈夫曼设计了一个贪心算法来构造最优前缀码,称为哈夫曼算法。它的正确性的证明也依赖于贪心的选择性质和最优子结构。接下来我们看看哈夫曼算法是怎么设计的。
      假定C是一个n个字符的几个,而其中的每个字符c∈C都是一个对象,其属性c.freq给出了字符的出现频率。算法自底向上地构造除了对应最优编码的二叉树。
    HuffmanCode(C){
        Q = C; //初始化最小优先队列Q
        for(i = 1 ~ n - 1){ //循环n-1次
            Node z;         //新节点z
            z.left = x = getMin(Q); 
            z.right = y = getMin(Q); //分别提取频次的两个最小值,x,y作为z的左右孩子
            z.freq = x.freq + y.freq; //新节点频次等于两个原来的最小频次之和
            insert(Q,z)               //将z插入到Q中,取代x,y,更新节点Q队列
        }
        return HFMroot;               //返回哈夫曼树的根节点 
    }
    
    • 说明:从|C|个叶子结点开始,执行|C|-1个“合并”操作创建出最终的二叉树。算法使用一个以属性freq为关键字最小优先队列Q,以识别两个最低频次的对象将其合并。当合并两个对象时,得到的新对象的频次设为原来两个对象的频率之和。
    • 对上文给出的例子,哈夫曼树的执行过程如(a)~(f)所示。初始队列大小为n = 6,需要合并五次来构造二叉树,最终的二叉树表示的就是最优前缀编码,一个字母与的码字为根节点到该字母叶子结点的简单路径上边标签的序列。
      哈夫曼树的构造过程
    • 说明分析:对上图给出的频次执行哈夫曼算法的过程。每一个部分显示了优先队列的内容,已经按照频率递增顺序排好序。在每个步骤,频率最低的两棵树进行合并。叶子结点用矩形表示,每个叶子结点包含一个字符及其频率。内部结点用圆圈表示,包含其孩子结点的频率之和。内部结点指向左孩子的边标记为0,指向右孩子的边标记为1。一个字母的码字对于从根节点到其叶子结点的路径上的边的标签序列。
      (a)将个字符的频次递增排序
      (b)~(f)找出前两个最小的频次元素,分别作为左右孩子(左右顺序随意),合并成一个新节点,频次为14,取代5,9,放入优先队列Q中,现在Q为:[12,13,14(带2节点),16,45];再从中取出12,13,分别作为左右孩子,合并是频次为25的节点,取代放入优先队列Q,现在Q为[14(带2节点),16,25(带2节点),45],以此类推…
      PS:优先队列Q也可以用有序表代替

    四、哈夫曼算法的Java语言实现

    import java.util.ArrayList;
    import java.util.HashMap;
    
    /**
     * 哈夫曼树节点类
     * @author hyn
     */
    class HFM_Node{
        int frequency; //频次
        HFM_Node left; //左孩子
        HFM_Node right;//右孩子
        char aChar;    //结点字符(叶子结点才有)
        String hfmCode = "";//结点的哈夫曼编码
    
        /**
         * 第一个构造方法
         * @param frequency 频次
         * @param left 左孩子
         * @param right 右孩子
         */
        public HFM_Node(int frequency, HFM_Node left, HFM_Node right){
            this.frequency = frequency;
            this.left = left;
            this.right = right;
        }
    
        /**
         * 第二个构造方法
         * @param frequency 频次
         * @param left 左孩子
         * @param right 右孩子
         * @param aChar 字符
         */
        public HFM_Node(int frequency, HFM_Node left, HFM_Node right, char aChar){
            this.frequency = frequency;
            this.left = left;
            this.right = right;
            this.aChar = aChar;
        }
    
    }
    
    /**
     * 哈夫曼树类
     * @author hyn
     */
    public class HFMTree {
        private ArrayList<HFM_Node> freqs = new ArrayList<>(); //节点队列Q
        private HFM_Node root;              //树根节点
        private int nodeNum;                //节点总数
        HashMap<Integer,Character> hashMap; //频次-字符字典
    
        /**
         * 用构造器直接构造哈夫曼树
         * @param map 用户传入的频次-字符 哈希表字典
         */
        public HFMTree(HashMap<Integer,Character> map){
            creatFreqArray(map);
            this.root = creatHFMTree();
        }
    
        /**
         * 创建频次有序表
         * @param map 储存频次及其对应字符的字典
         */
        private void creatFreqArray(HashMap<Integer,Character> map){
            this.hashMap = map;
            nodeNum = map.size();
            for (HashMap.Entry<Integer,Character> entry:map.entrySet()) {
                HFM_Node node = new HFM_Node(entry.getKey(),null,null,entry.getValue());
                freqs.add(node);
            }
            sort(); //更新表后排序
        }
    
        /**
         * ArrayList插入排序,使得数组内部结点关键字按升序排列
         */
        private void sort(){
            int n = freqs.size();
            for(int i = 1;i < n;i++){
                for(int j = i;j>0;j--){
                    if(freqs.get(j).frequency<freqs.get(j - 1).frequency){
                        HFM_Node temp = freqs.get(j);
                        freqs.set(j,freqs.get(j - 1));
                        freqs.set(j - 1,temp);
                    } else break;
                }
            }
        }
    
        /**
         * 创建哈夫曼树
         * @return 哈夫曼树根节点
         */
        private HFM_Node creatHFMTree(){
            for(int i = 1;i < nodeNum;i++){
                System.out.println("合并!");
                HFM_Node nodeX = freqs.remove(0); //当前最小的结点(就在队首)出队
                HFM_Node nodeY = freqs.remove(0); //当前最小的结点(就在队首)出队
                int newFreq = nodeX.frequency + nodeY.frequency; //新的节点的频次
                HFM_Node nodeZ = new HFM_Node(newFreq,nodeX,nodeY); //生成新节点,左右孩子别指向之前出队的两个节点
                freqs.add(nodeZ);
                sort(); //更新排序
            }
            this.root = freqs.get(0); //最后freqs队列就剩一个元素(哈夫曼树根节点)
            return root;
        }
    
        /**
         * 获取根节点
         * @return 哈夫曼树根节点
         */
        public HFM_Node getRoot() {
            return root;
        }
    
        /**
         * 先序遍历哈夫曼树进行哈夫曼编码并输出
         * 除了根节点,逐次求每一个结点的哈夫曼编码,以便求出最后叶子结点的哈夫曼编码
         * @param root 哈夫曼树根节点
         */
        private void preOrder(HFM_Node root){
            if(root != null){
                if (root.left != null) { //如果左节点非空。则对左节点进行编码并递归先序遍历
                    root.left.hfmCode = root.hfmCode+0; //向左走,字符串末尾加0
                    preOrder(root.left);
                }
    
                if(root.left == null && root.right == null){ //输出叶子结点的字符及其哈夫曼编码
                    System.out.println(hashMap.get(root.frequency)+":"+root.hfmCode);
                }
    
                if(root.right != null) { //如果右节点非空。则对右节点进行编码并递归先序遍历
                    root.right.hfmCode = root.hfmCode+1;//向右走,字符串末尾加1
                    preOrder(root.right);
                }
            }
        }
    
        public void preOrder(){
            preOrder(root);
        }
    
        public static void main(String[] args) {
            HashMap<Integer,Character> map = new HashMap<>();
            map.put(45,'a');
            map.put(13,'b');
            map.put(12,'c');
            map.put(16,'d');
            map.put(9,'e');
            map.put(5,'f');
            HFMTree t = new HFMTree(map);
            t.preOrder();
        }
    
    }
    
    

    说明:

    • 定义了两个类:哈夫曼树节点类和哈夫曼树类
    • 在哈夫曼节点类中定义了五个属性:字符及其频次和哈夫曼编码,左右孩子。以及2个有参构造方法。
    • 哈夫曼树类中有4个属性:节点队列Q,树根节点,节点总数,以及频次对应字符的哈希表。
    • 构造方法用来传入哈希表后直接构造哈夫曼树。
    • creatFreqArray方法用于把传入的哈希表的键值对逐个存入到Q中,用到ForEach循环进行哈希表遍历,存储完毕之后就对Q按频次进行插入排序。
    • creatHFMTree方法用来进行哈夫曼树的构造,参考上文的伪代码。
    • preOrder方法用递归的方式对哈夫曼树进行先序遍历,并进行哈夫曼编码操作并输出结果,编码方法是边遍历结点边对结点逐个编码,参考注释。
    • 运行结果:
      在这里插入图片描述

    OVER,如有更好的方法或有不足之处,欢迎指正!!!

    展开全文
  • NULL 博文链接:https://javaprince.iteye.com/blog/839949
  • 哈夫曼算法构造代码

    2020-12-31 22:22:22
     哈夫曼编码主要用于数据压缩。  哈夫曼编码是一种可变长编码。该编码将出现频率高的字符,使用短编码;将出现频率低的字符,使用长编码。  变长编码的主要问题是,必须实现非前缀编码,即在一个字符集中,任何一...
  • 哈夫曼算法,包括计算权重,将字符串编码为二进制串以及将二进制串解码为字符串。
  • 今天第一次接触到哈夫曼树,大概看了一遍,感觉太厉害了,忍不住 飙出了几个哇塞!哇塞! 哇塞! 然而仔细一想,就是那么一回事,只是我们还没想到而已, 在更好学习哈夫曼树之前,我们必须要先知道几个知识点。 1...

    今天第一次接触到哈夫曼树,大概看了一遍,感觉太厉害了,忍不住 飙出了几个哇塞!哇塞! 哇塞!   然而仔细一想,就是那么一回事,只是我们还没想到而已,

    在更好学习哈夫曼树之前,我们必须要先知道几个知识点。

    1.bit(位)--->byte(字节)

      带大家温习一下 计算机 如何存储数据的 , 计算机是很笨的 ,它只认识 0和1 ,相信各位应该都听说过吧! 如果它真的这么笨,那如何缔造出现在的互联网帝国呢 。那我们一起来见识一下吧!

            首先 知道一下   bit--->byte 的关系。  8bit==1byte .  每个 bit 的位置只能是 0或者1, 也就是说 一个字节是由 8个不同顺序的 0和1 组成的 。

            如   01001010  或者10110011 或者01110011  这样都算是一个字节,我们再想一下 ,           8个 0和1 组成的状态 是不是有256中可能性呢 ? 就是2的8次方。 (这里扩展一下 ,如果一个字节里面可以记录256种可能,那是不是就可以知道,一个字节可以代表256个不同的字符, 在ASCII编码表中,就是放着256个字符对应256种状态,其中包含英文大小写各种符号,大家有兴趣可以去了解一下 ASCII

    说了这么多 ,就是让大家知道一下 ,一个字节 在计算机中是对应着8个bit的 。

    下面我们来了解一下哈夫曼树

            hello word hello word

    上面这端字符串怎么变成一个哈夫曼树呢,是不是觉得很抽象? 不着急,我们慢慢庖丁解牛一下

    首先看上面的字符串中是不是有很多重复的字母或者符号存在,这样存储或者传输是否很耗费空间,数据量多那是不是容量就很大啦 ,那有木有  优化的方法呢 。答案是肯定的 ,我们来实现一下吧 。

     1.首先统计一下字符串的 重复出现次数、

        h:2,e:2,l:4,o:4,空格:3,w:2,r:2,d:2   

        我再按照次数排序一下

         h:2,e:2,w:2,r:2,d:2,空格:3,l:4,o:4 

    接下来 我们把这个排序好的集合变成哈夫曼树,又称最优的二叉树   记住哈夫曼树的一个特点 就是任何父节点都是空的

     我们从最小数的两个元素来构建二叉树 第一步

     

     那前两个元素构建一个二叉树后,又将这个二叉树放回集合,这个二叉树的统计数等于子集合的总数,所有等于4  ,然后我们再排序一下 

            

     接下来我们再拿最前面的两个元素构建二叉树

    然后排序, 依次类推构成的最终二叉树

     从这个二叉树中我们看到,出现频率多的元素 二叉树的层级是不是越小, 还有之前说过的,所有元素的节点 都是空的,为什么会这样呢 ,先给大家一个答案:保证一个元素在这颗树中唯一!

     接下来我们给这颗树的 树枝指定 0和1 ,一个最小的二叉树只有两个子节点,那我们把左边的树枝定义为 0 右边的定义为1 : 

    给这颗树标好树枝编号了,    那我们是不是可以通过树枝编号找到每个元素了。

    l       :00

    o      :01

    d      :100

    空格  :101

    w      :1100

    r        :1101

    h       :1110

    e       :1111

    咦!大家是不是有个疑问: 这么多 0和1 会不会找的时候会混乱呢 , 

    这个不用担心,因为你从树上面找元素的时候,一个元素所经过的节点都是空的 ,所以一个元素的树枝组成都是唯一滴。

    然后我们把上面的那段字符串通过树枝拼装一下:

    然后我们把树枝来拼装一下    

    111011110000011011100011101100101111011110000011011100011101100

    看到这么一串0和1组成的元素,大伙是不是想到了啥 ?

    对,就是用bit 来标识 ,上面有64个0和1 那我们是不是正好装到 8*8个bit里面了 ,就是 8个字节,

    我们看下原来的字符有多少个字节。

    hello word hello word  -----》 22个字节 ,

    是不是很哇塞    一下减少了 2倍多。。 这样是不是我们是不是可以用来实现压缩功能啦。太棒了!!!

    但是大家不要忘了一件事,  就是元素的字典,也就是刚才元素的统计集合, 我们在解压的时候,也要根据这个统计集合来还原数据哈 。 

    所以在数据量少的情况下 你用哈夫曼算法压缩 你的容量有可能还会变大。哈哈哈哈 !

    不过不用担心,英文字母字符才有128个,常用的汉字不过才几千个 。汉字最多不过才几万个。

    所以字符的字典肯定有限的, 你平常发现一个文件很小的时候,压缩发现没啥用 吧 。

    所以内容越多,重复出现的字符越多,通过哈夫曼算法压缩后的内存节省比例就越高!

    这是 本人这个对哈夫曼算法的理解和分析,如有不足或者错误之处,请大家多多指教,如有不明白的地方 可以评论留言,我们一起讨论一下哦 。这个是理论篇, 下一篇我将用c# 实现 哈夫曼算法实现压缩。  下期见咯 老铁们!!!

    哈夫曼算法 原理解析--通俗易懂篇(实战压缩篇)(二)

    展开全文
  • 该代码实现哈夫曼基本算法,并对输入的图片进行压缩,最终输出译码后的图片,代码真实可用,想理解学习哈夫曼压缩的同学可以看看。
  • 哈夫曼编码与哈夫曼算法 哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均...

    哈夫曼编码与哈夫曼算法

    哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)

    哈夫曼编码在文件压缩上的应用:

    哈夫曼编码可以运用在对文件字符的编码上,以实现对文件的压缩。
    加入一个文件中存在a,b,c,d,e这几种字符,文件的整体是由这5中字符构成的,如果我们要用二进制编码这个文件,那么就需要对每一个字符分别进行二进制编码来对它们进行区分。
    假设这5中字符在文件中出现的频率分别是:5,12,6,3,9
    如果我们用一般的方式来进行编码,要使用同长度的2进制码区分五种字符,那么我们需要长度为3bit的编码来表示,即
    a——000;b——001;c——010;d——011;e——100;
    这样对文件中的字符编码在正确性上是没有问题的,但是我们可以计算出文件编码后的总大小为:35*3=105bit

    哈夫曼编码用特殊的编码方式来使这样的文件在编码后占用的bit总数更小:
    哈夫曼编码的特殊性在于,使字符编码的长度可以不相同,对应上面的例子来说也就是说每一个字符的二进制编码表示不一定长度都为3。
    如果我们才用以下这样的方式去编码这文件中的5个字符:
    a——000;b——01;c——10;d——001;e——11;
    那么我们再次计算一下编码后文件的总大小:35+212+26+33+2*9=78bit
    结果明显比一般的同长度的编码方式减小了。但是要注意,这样的编码方式使每个字符编码不存在二义性的前提是每一个字符编码都不能是其他字符编码的前缀。

    如何获得哈夫曼编码(最优编码)

    通过构造哈夫曼树获得哈夫曼编码:

    1.哈夫曼树
    哈夫曼树是这样的一棵树:给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
    在这里插入图片描述

    简单的来讲,就是说哈夫曼树是要使树中的每一个节点的权值与这个节点的深度的乘积的和最小的树。那么其实这样的哈夫曼树的特点就是,权值越大的节点离根节点越近,拥有较小的深度,而权值最大的节点往往就作为根节点;权值越小的节点离根节点越远,权值最小的树一定是叶子节点。 上图所示就是一颗哈夫曼树,也称作最优二叉树。

    2.哈夫曼算法

    最优哈夫曼编码树的创建

    我们可以通过以下哈夫曼算法来创建一颗最优哈夫曼编码树:
    对于我们最开始举得例子而言,一共存在5个字符a,b,c,d,e,且其出现的频率分别为:5,12,6,3,9
    我们将每一个字符设置为一个节点,并且这个节点的权值就是字符出现的频率。
    现在我们将每一个单独的节点看作是一棵树,这样一来我们就得到了一个森林。
    森林中一共有5棵树,a,b,c,d,e,树的权值分别为5,12,6,3,9.
    在算法的开始,我们首先找到森林中任意两颗权值最小的树,并将它们分别作为新树的两个孩子节点来合并,其中左右孩子节点的顺序可以是任意的。这里我们就找到了节点a和d,将它们作为树T1的两个孩子节点得到新树T1,重复这样的过程,直到全部单节点树都被合并,最终整个森林合并为一棵树为止。此时我们得到树就是最优哈夫曼编码树。
    我们可以通过哈夫曼编码树得到哈夫曼编码,方法是从根节点开始到任意一个叶子节点的路径上,如果经过向左的分支则得到0,经过向右的分支则得到1,用上图中的哈夫曼树举例子,叶子节点E的编码即为000,叶子节点A的编码即为01.
    下面我们给出得到最优哈夫曼编码树的c语言代码:

    #include <stdio.h>
    #include <stdlib.h>
    //定义哈夫曼树和节点 
    typedef struct HTreeNode
    {
    	char ch;
    	int weight;
    	struct HTreeNode *left;
    	struct HTreeNode *right;
    }HTreeNode,*HTree;
    //定义森林 
    typedef struct forest
    {
    	int tree_num;
    	int *book;
    	int *weight;
    	HTreeNode *trees;
    }forest;
    //初始化森林 
    forest *Init_forest(int tree_num)
    {
    	int i,ch,weight;
    	forest * f=(forest*)malloc(sizeof(forest));
    	f->tree_num=tree_num;
    	f->book=(int *)calloc(tree_num,sizeof(int));
    	f->weight=(int *)calloc(tree_num,sizeof(int));
    	f->trees=(HTreeNode *)calloc(tree_num,sizeof(HTreeNode));
    	for(i=0;i<tree_num;i++)
    	{
    		printf("\n请输入字符:");
    		scanf("%c",&ch); getchar();
    		printf("\n请输入字符出现频率:");
    		scanf("%d",&weight); getchar();
    		f->trees[i].ch=ch;
    		f->trees[i].weight=weight;
    		f->weight[i]=weight;
    		f->trees[i].left=NULL;
    		f->trees[i].right=NULL;
    	}
    	return f;
    }
    //在遍历树的过程中计算树的权值 
    void OrderTree(HTree t,int * weight,int depath)
    {
    	if(t->left==NULL&&t->right==NULL)
    	{
    		(*weight)+=(t->weight)*depath;
    		return;
    	}
    	OrderTree(t->left,weight,depath+1);
    	OrderTree(t->right,weight,depath+1);	
    }
    //得到树的权值 
    int get_weight(HTree t)
    {
    	int weight=0;
    	OrderTree(t,&weight,0);
    	return weight;
    }
    //合并两棵树
    int UnionTrees(forest *f)
    {
    	
    	HTree t1=NULL,t2=NULL,t3=(HTree)malloc(sizeof(HTreeNode));
    	HTree leftchild=(HTree)malloc(sizeof(HTreeNode)),rightchild=(HTree)malloc(sizeof(HTreeNode));
    	int index1,index2;
    	int i,j,k;
    	int min_weight,index;
    	for(k=1,i=0;k<=2;k++,i=0)
    	{
    	while(f->weight[i]==0||(&f->trees[i])==t1) i++;	
    	index=i;
    	min_weight=f->weight[i];
    	for(i=0;i<f->tree_num;i++)
    	{
    		if((&f->trees[i])==t1) continue;
    		if(min_weight>f->weight[i]&&f->weight[i]!=0)
    		{
    			
    			min_weight=f->weight[i];
    			index=i;
    		}
    	}
    	if(k==1) 
    	{
    	    t1=&(f->trees[index]);
    	    index1=index;
        }
    	else 
    	{
    		t2=&(f->trees[index]);
    		index2=index;
    	}	
    }
        *leftchild=*t1;
        *rightchild=*t2;
        t3->left=leftchild;
    	t3->right=rightchild;
    	//t3->weight=get_weight(t3);
    	t3->weight=t1->weight+t2->weight;
    	f->trees[index1]=*t3;
    	f->weight[index1]=t3->weight;
    	f->weight[index2]=0;
     return index1;
    }
    //遍历哈夫曼树以得到哈夫曼编码 
    void get_code(HTree t,int code[],int step)
    {
    	if(t==NULL) return;
    	if(t->left==NULL&&t->right==NULL)
    	{
    		int i=0;
    		for(i=0;i<step;i++) printf("%d ",code[i]);
    		printf(" %c\n",t->ch);
    		return;
    	}
    		code[step]=0;
    		get_code(t->left,code,step+1);
    		code[step]=1;
    		get_code(t->right,code,step+1);
     } 
    //Huffman算法
    Huffman(forest *f) 
    {
    	HTree ht;
    	int i,j,k,index;
    	int n=f->tree_num,code[n];
    	for(i=0;i<n-1;i++)
    	{
    		index=UnionTrees(f);//将森林中的n棵树合并n-1次最后得到最优的那颗Huffman树 
    	}
    	ht=&f->trees[index];
    	printf("\n哈夫曼树的总权值为: %d \n",get_weight(ht));
    	printf("\n哈夫曼编码为:\n");
    	get_code(ht,code,0);
    }
    main()
    {
        int n,i,j,k;
        printf("请输入要进行编码的字符总类数:");
        scanf("%d",&n);
        getchar();
        forest *f=Init_forest(n);
        Huffman(f);
    }
    

    在这里插入图片描述

    展开全文
  • 摘要:全面介绍了范式哈夫曼算法的理论基础和实现方式。详细讨论了编码位长计算、限制编码位长、解码优化、码表二次压缩等实现技术。并给出了一个切实可行的应用程序。
  • 根据字母出现的频率,运用哈夫曼树对字母进行编码,首先用哈夫曼算法建立一棵哈夫曼树。然后自下而上依次遍历每个叶子结点与根结点的路径,若当前结点是双亲结点的左结点,则编码字符串加0,右结点,加1。解码则依次...
  • 数据结构实验哈夫曼算法实验报告
  • 定义:给定n个权值作为n个叶子结点,构造一棵二叉树,若树的带权路径长度达到最小,则这棵树被称为哈夫曼树。
  • 哈夫曼算法的应用.doc

    2022-05-07 19:04:36
    哈夫曼算法的应用.doc
  • 大家都知道,使用哈夫曼压缩能达到无损压缩,也就是说。保证了原图质量的同时,能够降低图片的大小。这是什么原理呢?首先我们需要了解的是...性能差),由于哈夫曼算法非常吃CPU,被迫用了其他的算法。所以Sk...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 20,766
精华内容 8,306
关键字:

哈夫曼算法

友情链接: 字符计数器.rar