算导实验六 哈夫曼编码

实验要求:

       编程实现Huffman编码问题,并理解其核心思想。

       对字符串进行01编码,输出编码后的01序列,并比较其相对于定长编码的压缩率。例如对于字符串“AABBBEEEEGZ”,如果使用定长编码,‘A’、‘B’、‘E’、‘G’、‘Z’字符各需要3位01串编码,编码后的字符长度为3*11=33位,如果使用Huffman编码,可编码为下图,编码后的字符长度为2*3+3*2+4*1+4+4=24,压缩率为24/33=72.73%.

        对文件data.txt的字符串按照Huffman编码方式编码为01序列,并输出到encode.txt文件,控制台打印压缩率。

1.实验内容

        Huffman编码可以有效的压缩数据,通常可以节省20%~90%的空间,具体压缩率依赖于数据的特性。本实验实现Huffman树的生成,完成Huffman编码的过程。

2.实验目的

        1.掌握Huffman树的概念、存储结构

        2.掌握建立Huffman树和Huffman编码的方法及带权路径长度的计算

        3.掌握二叉树的应用

3.Huffman树结点定义

        Huffman树的结点具有一个数据域data ,用来存放该结点所代表的数据;freq 表示data 在待编码序列中出现的频数;leftright 指明了当前节点的左孩子和右孩子;code 为当前节点的Huffman编码。

public class Node<T> implements Comparable<Node<T>>
{
    private T data; //数据域
    private int freq;   //数据出现的频率
    private Node<T> left;   //左孩子
    private Node<T> right;  //右孩子
    String code;    //编码
}

在结点中重写了compareTo 方法来实现将结点按照freq 从大到小排序。

    //从大到小排序
    @Override
    public int compareTo(Node<T> other)
    {
        if (other.getFreq() > this.getFreq())
        {
            return 1;
        }
        if (other.getFreq() < this.getFreq())
        {
            return -1;
        }

        return 0;
    }

4.Huffman树的构造及编码

4.1Huffman树的构造

        HuffmanTree 类包括一个Node 类型的属性root ,为Huffman树的树根。creatTree 方法来构造一棵Huffman树,该方法接收一个由待编码序列中的各个不同的数据所创建的对象的列表nodes ,该方法执行结束后,Huffman树创建完成,root 中保存了该树的树根。

        creatTree 首先判断列表nodes 中的元素个数,若nodes.size()>1 ,则进入while 循环;当条件不满足时,nodes 中只剩一个结点,也就是树根节点。

while (nodes.size() > 1)    //直到list中剩一个结点也就是树根

进入循环后,首先将nodes 中的结点按照freq 从大到小进行排序

Collections.sort(nodes);    //按从大到小排序

排序结束后,nodes 的倒数第一个和倒数第二个元素分别为列表中freq 最小和次小的元素,按照Huffman树的构件规则,选取这两个结点为左右孩子构建其父结点,父结点的datanullfreq 为这两个结点的freq 之和。对两个孩子进行编码,这里规定倒数第二个结点编码为0,为左孩子;倒数第一个结点编码为1,为右孩子。

Node<T> left = nodes.get(nodes.size() - 2); //倒数第二个结点设为左孩子 倒数第二小
left.setCode(0 + "");   //编码为0
Node<T> right = nodes.get(nodes.size() - 1);    //倒数第一个结点设为右孩子 最小
right.setCode(1 + "");  //编码为1

//左右孩子的父结点的权值为两个孩子权值之和
Node<T> parent = new Node<>(null, left.getFreq() + right.getFreq());
parent.setLeft(left);
parent.setRight(right);

将左右孩子从列表中删除,并将父结点加入的列表中,至此一次循环结束。以列表中剩余的结点继续重复上述过程,便可构建其一棵Huffman树。

 //将左右孩子删除并把父结点放入list中
 nodes.remove(left);
 nodes.remove(right);
 nodes.add(parent);

循环结束后,nodes 中有且只有一个结点,为Huffman树的根节点,将其赋给root

root = nodes.get(0);    //树根赋值

4.2序列编码

        用HuffmanTree 中的广度优先遍历breadth 方法遍历整棵树,并将结果保存到哈希表codeSet 中,key 为结点的datavalue 为结点的Huffman编码。对待编码序列进行编码,遍历待编码序列,每一个字符在codeSet 中寻找其对应的编码,即可完成对待编码序列的编码。

//广度优先遍历,存放字符与对应的编码
HashMap<Character, String> codeSet = huffmanTree.breadth(huffmanTree.getRoot());
for (int i = 0; i < str.length(); i++)
{
    codeStr.append(codeSet.get(str.charAt(i)));
}

4.3压缩率计算

        压缩率的定义为Huffman编码的位数与传统编码的位数之比。Huffman编码的位数由codeStr.length() 容易求得。传统编码位数的计算由

Integer.toBinaryString(codeSet.size()).length() * str.length()

求得。式中codeSet.size() 为待编码序列中不同数据的个数,将其转为二进制字符串并求其长度即可得到传统编码下每一数据的编码长度,再乘上序列的长度即可得到传统编码的编码长度。

 rate = (double) codeStr.length() / (Integer.toBinaryString(codeSet.size()).length() * str.length());

5.算法测试结果

输入字符串为“AASMABBAAARRAABCAACCRRSNEEFF”,其中各个元素的个数如下表

元素出现次数
A10
B3
C3
E2
F2
M1
N1
R4
S2

根据Huffman树的编码规则可以得到各个字符得到编码为

元素编码
A1
B0000
C0001
E0111
F0100
M01010
N01011
R001
S0110

则Huffman编码得到的长度为80,普通编码的长度为112,压缩率为71.4%。输出结果如下图所示

6.实验过程中遇到的困难及收获

        Huffman之所以能够压缩编码长度,是因为采用了变长编码的技术,其思想是赋予高频字符短码字,赋予低频字符长码字。Huffman编码采用的是前缀码,即没有任何码字是其他码字的前缀。前缀码的作用是简化解码过程,由于没有码字是其他码字的前缀,因此解码的过程是无歧义的,二进制串可以唯一的解析。

        Huffman树的构造过程采用了贪心的思想,每次贪心的选取出现频率最低的字符,这样可以保证低频字符出现在树的深层次,码字长;高频字符出现在浅层次码字短,即达到了压缩编码长度的目的。

7.实验源代码

Node.java

//哈夫曼树节点类
public class Node<T> implements Comparable<Node<T>>
{
    private T data; //数据域
    private int freq;   //数据出现的频率
    private Node<T> left;   //左孩子
    private Node<T> right;  //右孩子
    String code;    //编码

    public Node(T data, int freq)
    {
        this.data = data;
        this.freq = freq;
        this.code = "";

    }

    public T getData()
    {
        return data;
    }

    public int getFreq()
    {
        return freq;
    }

    public Node<T> getLeft()
    {
        return left;
    }

    public void setLeft(Node<T> left)
    {
        this.left = left;
    }

    public Node<T> getRight()
    {
        return right;
    }

    public void setRight(Node<T> right)
    {
        this.right = right;
    }

    public String getCode()
    {
        return code;
    }

    public void setCode(String str)
    {
        code = str;
    }

    @Override
    public String toString()
    {
        return "data:" + this.data + ";weight:" + this.freq + ";code: " + this.code;
    }

    //从大到小排序
    @Override
    public int compareTo(Node<T> other)
    {
        if (other.getFreq() > this.getFreq())
        {
            return 1;
        }
        if (other.getFreq() < this.getFreq())
        {
            return -1;
        }

        return 0;
    }
}

HuffmanTree.java

/*
 * @Title:
 * @Package
 * @Description:
 * @author yangf257
 * @date 2021/12/14  17:51
 */

import java.util.*;

public class HuffmanTree<T>
{
    private Node<T> root;

    public Node<T> getRoot()
    {
        return root;
    }

    public void setRoot(Node<T> root)
    {
        this.root = root;
    }

    public void createTree(List<Node<T>> nodes)
    {
        while (nodes.size() > 1)    //直到list中剩一个结点也就是树根
        {
            Collections.sort(nodes);    //按从大到小排序

            Node<T> left = nodes.get(nodes.size() - 2); //倒数第二个结点设为左孩子 倒数第二小
            left.setCode(0 + "");   //编码为0
            Node<T> right = nodes.get(nodes.size() - 1);    //倒数第一个结点设为右孩子 最小
            right.setCode(1 + "");  //编码为1

            //左右孩子的父结点的权值为两个孩子权值之和
            Node<T> parent = new Node<>(null, left.getFreq() + right.getFreq());
            parent.setLeft(left);
            parent.setRight(right);

            //将左右孩子删除并把父结点放入list中
            nodes.remove(left);
            nodes.remove(right);
            nodes.add(parent);
        }
        root = nodes.get(0);    //树根赋值
    }

    //广度优先遍历
    public HashMap<T,String> breadth(Node<T> root)
    {
        Queue<Node<T>> queue = new ArrayDeque<>();
        HashMap<T, String> map = new HashMap<>();

        if (root != null)
        {
            queue.offer(root);
            root.getLeft().setCode(root.getCode() + "0");   //左孩子为0
            root.getRight().setCode(root.getCode() + "1");  //右孩子为1
        }

        while (!queue.isEmpty())
        {
            map.put(queue.peek().getData(), queue.peek().getCode());
            Node<T> node = queue.poll();
            if (node.getLeft() != null) node.getLeft().setCode(node.getCode() + "0");
            if (node.getRight() != null) node.getRight().setCode(node.getCode() + "1");

            if (node.getLeft() != null)
            {
                queue.offer(node.getLeft());
            }

            if (node.getRight() != null)
            {
                queue.offer(node.getRight());
            }
        }
        return map;
    }
}

HuffmanCodeTest.java

/*
 * @Title:
 * @Package
 * @Description:
 * @author yangf257
 * @date 2021/12/14  19:34
 */


import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class HuffmanCodeTest
{
    public static void main(String[] args)
    {
//        String str = "AABBBEEEEGZ";
        String str = readFile();    //需要处理的字符串
        StringBuilder codeStr = new StringBuilder(); //编码过后的字符串
        HuffmanTree<Character> huffmanTree = new HuffmanTree<>();
        huffmanTree.createTree(preOper(str));

        //广度优先遍历,存放字符与对应的编码
        HashMap<Character, String> codeSet = huffmanTree.breadth(huffmanTree.getRoot());
        for (int i = 0; i < str.length(); i++)
        {
            codeStr.append(codeSet.get(str.charAt(i)));
        }

        double rate;
        //codeSet.size():字符串中不同字符的个数
        //toBinaryString:将字符串中不同字符的个数转化为二进制字符串,然后求其长度即为传统编码下的一个字符长度
        rate = (double) codeStr.length() / (Integer.toBinaryString(codeSet.size()).length() * str.length());
        System.out.println(rate);

        try
        {
            File writeName = new File(".\\6.HuffmanCode\\encode.txt");
            writeName.createNewFile();
            BufferedWriter out = new BufferedWriter(new FileWriter(writeName));
            out.write(codeStr.toString());
            out.flush();
            out.close();

        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }


    //读文件
    public static String readFile()
    {
        String pathname = ".\\6.HuffmanCode\\data.txt";
        String nodestr = "";
        try (FileReader reader = new FileReader(pathname); BufferedReader br = new BufferedReader(reader) // 建立一个对象,它把文件内容转成计算机能读懂的语言
        )
        {
            String line = "";
            while ((line = br.readLine()) != null)
            {
                nodestr += line;
            }
        } catch (IOException e)
        {
            e.printStackTrace();
        }
        return nodestr;
    }

    /**
     * 预处理,把每一个字符创建为Node对象,并保存在nodes列表中
     * @param nodestr:需要处理的字符串
     */
    public static List<Node<Character>> preOper(String nodestr)
    {
        HashMap<Character, Integer> map = new HashMap<>();
        for (int i = 0; i < nodestr.length(); i++)
        {
            map.put((nodestr.charAt(i)), map.getOrDefault(nodestr.charAt(i), 0) + 1);
        }

        List<Node<Character>> nodes = new ArrayList<>();
        for (char key : map.keySet())
        {
            Node<Character> node = new Node<>(key, map.get(key));
            nodes.add(node);
        }
        return nodes;
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
哈夫曼编码是一种用于数据压缩的编码方法,它通过将出现频率较高的字符用较短的编码表示,而将出现频率较低的字符用较长的编码表示,从而实现对数据的压缩。下面是一个演示哈夫曼编码的例子: 假设有一个文本文件SourceFile.txt,其中包含一段文本。首先,需要统计每个字符在文本中出现的频率,并根据频率构建哈夫曼树。然后,根据哈夫曼树生成每个字符的编码表。最后,根据编码表将文本中的字符转换成相应的编码,并将编码结果保存到另一个文件ResultFile.txt中。 以下是一个Python实现的示例代码: ```python import heapq import os class HuffmanNode: def __init__(self, char, freq): self.char = char self.freq = freq self.left = None self.right = None def __lt__(self, other): return self.freq < other.freq def build_frequency_table(text): frequency_table = {} for char in text: if char in frequency_table: frequency_table[char] += 1 else: frequency_table[char] = 1 return frequency_table def build_huffman_tree(frequency_table): heap = [] for char, freq in frequency_table.items(): node = HuffmanNode(char, freq) heapq.heappush(heap, node) while len(heap) > 1: node1 = heapq.heappop(heap) node2 = heapq.heappop(heap) merged_node = HuffmanNode(None, node1.freq + node2.freq) merged_node.left = node1 merged_node.right = node2 heapq.heappush(heap, merged_node) return heap[0] def build_encoding_table(huffman_tree): encoding_table = {} def build_encoding_table_helper(node, code): if node is None: return if node.char is not None: encoding_table[node.char] = code build_encoding_table_helper(node.left, code + "0") build_encoding_table_helper(node.right, code + "1") build_encoding_table_helper(huffman_tree, "") return encoding_table def encode_text(text, encoding_table): encoded_text = "" for char in text: encoded_text += encoding_table[char] return encoded_text def decode_text(encoded_text, huffman_tree): decoded_text = "" current_node = huffman_tree for bit in encoded_text: if bit == "0": current_node = current_node.left else: current_node = current_node.right if current_node.char is not None: decoded_text += current_node.char current_node = huffman_tree return decoded_text # 读取源文件 with open("SourceFile.txt", "r") as file: text = file.read() # 构建频率表 frequency_table = build_frequency_table(text) # 构建哈夫曼树 huffman_tree = build_huffman_tree(frequency_table) # 构建编码表 encoding_table = build_encoding_table(huffman_tree) # 编码文本 encoded_text = encode_text(text, encoding_table) # 将编码结果保存到文件 with open("ResultFile.txt", "w") as file: file.write(encoded_text) # 解码文本 decoded_text = decode_text(encoded_text, huffman_tree) print("Huffman编码完成!") ``` 请注意,上述代码中的SourceFile.txt是输入文件,ResultFile.txt是输出文件。你可以根据实际情况修改这两个文件的路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值