哈夫曼编码
假如说,我们有下面这一个原始的字符串序列:
想对它进行哈夫曼编码,进行数据压缩存储,甚至通过网络发送存储我们的某一个设备中。
我们要对这个原始的字符串进行哈夫曼编码,我们首先要构建一棵最佳判定树!
我们在字符进行编码的时候,最好是把出现频率高的编码短一点,把出现频率低的编码长一点,因为出现频率高的意味着在原始序列中这个字符出现的次数多,所以编码短一点,有助于最后生成的压缩文件,总体上来说文件大小是比较小的,出现频率低的字符的编码长一点,这个也无所谓了,因为字符出现的次数本来就偏低,不会大量占用文本的空间位置。
以这样构建出来的和哈夫曼树,最后想得到的这个原始字符串序列的哈夫曼编码,是最优的。
构建一棵哈夫曼树,我们首先需要知道:
通过遍历,统计每个字符出现的次数:权值
构建哈夫曼树已经详细的叙述了:
因为这个集合有很多个一样的最小权值(2),我们假设取出E和G先来构建二叉树。
权值最大的(A:3)离根节点最近。
我们计算一下带权路径长度和
2*2+3*2+2*3+2*3+2*3+2*3=带权路径最短的!!!
获取字符的哈夫曼编码
对于这棵哈夫曼树来说,往左走,标识0,往右走,标识1
然后从根节点开始走到这个字符叶子节点上的路径的所有标识依次连起来,就是该字符的哈夫曼编码了。
哈夫曼编码:立刻可解码性
也就是说,如果我读到某一个编码串,我就能立即知道这是哪个字符的编码!----立刻可解码性
哈夫曼编码的另一个特点:变长编码
但是,哈夫曼编码是变长编码:
数据压缩
因为哈夫曼编码是用来数据压缩的,数据压缩以后,文件的长度越小越好,编码越短的字符说明离根节点越近,离根节点越近,说明这个字符出现的权值越大(频率高),所以编码越短一点,使得压缩的文件的大小是最短的。
编码以后都是0或者1组成的,0-1码,不可能用一个字符来存一个0或者1的,如果记录在文件或者通过网络发送,我们直接当做二进制进行存储。
如果我们存储的是整数,4个字节,8个位1个字节,32位
总共34个位,34/32,我们用5个字节存储这个二进制序列就够了。
而原始的字符串序列,一共13个字符,一共字符1个字节,总共就是14个字节(加上\0)
我们来遍历原始的序列。
首先我们有一颗哈夫曼树,一个指针指向根节点,一边遍历哈夫曼编码,一边遍历哈夫曼树。
我们从根节点和哈夫曼编码串的首元素,一起遍历
第一个是0,0就是往左走,然后遍历到1,往右走,就发现这个节点的左右子树都为空了,就是到叶子节点了,也就是说,根据立刻可解码性,不用等待01后面还有什么,01除了字符A,不会再是其他字符编码的前缀了。
然后把指针重新指向根节点,然后从哈夫曼编码串的01后的那个数字,继续一起遍历。以此类推。
哈夫曼树结合哈夫曼编码串,可以把原始的数据序列解码出来。
权值也可以存的是百分比哦。
哈夫曼编码和解码C++代码实现
#include <iostream>
#include <unordered_map>
#include <string>
#include <queue>
#include <functional>
using namespace std;
using uint = unsigned int;
//实现哈夫曼树
class HuffmanTree
{
public:
HuffmanTree()
: minHeap_([](Node* n1, Node* n2)->bool
{return n1->weight_ > n2->weight_; })
, root_(nullptr)
{}
//创建哈夫曼树
void create(string str)
{
//先统计字符的权值
unordered_map<char, uint> dataMap;
for (char ch : str)
{
dataMap[ch]++;
}
//生成节点,放入小根堆中
for (auto& pair : dataMap)
{
minHeap_.push(new Node(pair.first, pair.second));
}
while (minHeap_.size() > 1)
{
//获取两个权值最小的
Node* n1 = minHeap_.top();
minHeap_.pop();
Node* n2 = minHeap_.top();
minHeap_.pop();
//生成父节点
Node* node = new Node('\0', n1->weight_ + n2->weight_);
node->left_ = n1;
node->right_ = n2;
minHeap_.push(node);
}
root_ = minHeap_.top();
minHeap_.pop();
}
//encode
string encode(string str)
{
getHuffmanCode();
string encode_str;
for (char ch : str)
{
encode_str.append(codeMap_[ch]);
}
return encode_str;
}
//decode
string decode(string encode)
{
string decode_str;
Node* cur = root_;
for (char ch : encode)
{
if (ch == '0')
{
cur = cur->left_;
}
else
{
cur = cur->right_;
}
if (cur->left_ == nullptr && cur->right_ == nullptr)
{
decode_str.push_back(cur->data_);
cur = root_;
}
}
return decode_str;
}
private:
struct Node
{
Node(char data, uint weight)
: data_(data)
, weight_(weight)
, left_(nullptr)
, right_(nullptr)
{}
char data_;//字符数据
uint weight_;//节点的权值
Node* left_;//指向左孩子节点
Node* right_;//指向右孩子节点
};
private:
//输出哈夫曼编码
void getHuffmanCode()
{
string code;
getHuffmanCode(root_, code);
/*for (auto& pair : codeMap_)
{
cout << pair.first << " : " << pair.second << endl;
}
cout << endl;*/
}
void getHuffmanCode(Node* root, string code)
{
//VLR
if (root->left_ == nullptr && root->right_ == nullptr)
{
codeMap_[root->data_] = code;
return;
}
getHuffmanCode(root->left_, code + "0");
getHuffmanCode(root->right_, code + "1");
}
private:
Node* root_;//指向根节点
unordered_map<char, string> codeMap_;//存储字符对应的哈夫曼编码
using MinHeap = priority_queue<Node*, vector<Node*>, function<bool(Node*, Node*)>>;
MinHeap minHeap_;
};
int main()
{
string str = "ABACDAEFDEGGAHGNNMBHJGUTUYHBBNBNBJHGSUYTUYTRFHGTHJ";
HuffmanTree htree;
htree.create(str);
string encode = htree.encode(str);
cout << "encode:" << encode << endl;
cout << "decode:" << htree.decode(encode) << endl;
}
哈夫曼编码和解码java代码实现
package com.fixbug;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
/**
* 哈夫曼树的实现
*/
class HuffmanTree{
//统计字符的个数,即字符:权值
private Map<Character, Integer> charMap;
//小根堆存储Entry节点,通过权值比较节点大小
private PriorityQueue<Entry> queue;
//指向哈夫曼树的根节点
private Entry root;
//存储字符以及最终的哈夫曼编码
private Map<Character, String> codeMap;
public HuffmanTree() {
charMap = new HashMap<>();
//生成优先级队列,自定义通过Entry节点的权值进行比较
queue = new PriorityQueue<Entry>((a, b)->{
return a.getCount().compareTo(b.getCount());//比较器
});
codeMap = new HashMap<>();
}
/**
* 生成哈夫曼树
* @param str
*/
public void createHuffmanTree(String str) {
//1.统计字符串中字符出现的频率
for(int i=0; i < str.length(); ++i){
Integer cnt = charMap.get(str.charAt(i));//通过map集合获取这个字符出现的次数
if(cnt == null){//这个字符第一次出现
charMap.put(str.charAt(i), 1);
} else {
charMap.put(str.charAt(i), cnt + 1);
}
}
//2.把带权值的节点都添加到优先级队列当中 构建小根堆,因为每次是取出当前的最小的权值的2个节点
charMap.forEach((ch, count)->{
Entry entry = new Entry(ch, count);
queue.offer(entry);
});
//3.循环处理小根堆,每次拿两个权值最小的节点进行合并生成二叉树,不断重复
while (queue.size() > 1){
Entry entry1 = queue.poll();//出队
Entry entry2 = queue.poll();//从队列里取出2个节点,因为是优先级队列,小根堆,每次拿出来都是权值最小的
Entry entry = new Entry('\u0000', entry1.getCount()+entry2.getCount());//生成新的节点
entry.setLeft(entry1);//连接起来,构建二叉树
entry.setRight(entry2);
queue.offer(entry);//入队
}
//4.指向哈夫曼树的根节点
this.root = queue.poll();
}
/**
* 打印哈夫曼编码
* @return
*/
public void showCode() {
String str = "";
showCode(this.root, str);//从根节点开始
codeMap.forEach((ch, code)->{
System.out.println(ch + " code: " + code);
});
}
private void showCode(Entry root, String str) {
if(root.getLeft() == null && root.getRight() == null){
codeMap.put(root.getCh(), str);
return;
}
showCode(root.getLeft(), str + "0");
showCode(root.getRight(), str + "1");
}
/**
* 根据生成的哈夫曼编码,把str原始序列的编码进行返回
* @param str
* @return
*/
public String encode(String str) {
StringBuilder sbuf = new StringBuilder();
for(int i=0; i< str.length(); ++i){
sbuf.append(codeMap.get(str.charAt(i)));
}
return sbuf.toString();//转成字符串
}
/**
* encode是哈夫曼编码,根据上面的哈夫曼树,进行数据解码,返回
* @param encode
* @return
*/
public String decode(String encode) {
StringBuilder sbuilder = new StringBuilder();
Entry cur = this.root;//指向根节点
for(int i=0; i < encode.length(); ++i){
if(encode.charAt(i) == '0'){
cur = cur.getLeft();
} else {
cur = cur.getRight();
}
//访问到根节点了
if(cur.getLeft() == null && cur.getRight() == null){
sbuilder.append(cur.getCh());
cur = this.root;
}
}
return sbuilder.toString();
}
/**
* 哈夫曼树节点的类型
*/
static class Entry{
Character ch;//字符
Integer count;//权值
Entry left;//指向左孩子
Entry right;//指向右孩子
public Entry(char ch, int count) {//构造函数
this.ch = ch;
this.count = count;
this.left = this.right = null;
}
public Character getCh() {
return ch;
}
public void setCh(Character ch) {
this.ch = ch;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public Entry getLeft() {
return left;
}
public void setLeft(Entry left) {
this.left = left;
}
public Entry getRight() {
return right;
}
public void setRight(Entry right) {
this.right = right;
}
}
}
/**
* Hello world!
*/
public class //哈夫曼编码
{
public static void main( String[] args ) throws Exception{
String str = "soiyufhsjadfkndgiuyeruwighjhjsgyeugyfwhjkhggyuewhjdb";
HuffmanTree ht = new HuffmanTree();//生成一棵哈夫曼树
ht.createHuffmanTree(str);//构建一棵哈夫曼树
ht.showCode();//打印哈夫曼编码
String encode = ht.encode(str);
System.out.println(encode);
//解码
System.out.println(ht.decode(encode));
}
}
文件压缩和解压缩代码实现
哈夫曼树的节点类型:Entry.java
package com.fixbug.compress;
/**
* 哈夫曼树的节点类型
*/
public class Entry {
private Character ch;// 字符数据
private Integer count;//字符的频率
private Entry left;
private Entry right;
public Entry(Character ch, Integer count) {
this.ch = ch;
this.count = count;
}
public Character getCh() {
return ch;
}
public void setCh(Character ch) {
this.ch = ch;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public Entry getLeft() {
return left;
}
public void setLeft(Entry left) {
this.left = left;
}
public Entry getRight() {
return right;
}
public void setRight(Entry right) {
this.right = right;
}
}
我们要把字符串哈夫曼编码转成字节,然后把字节还原成哈夫曼编码
文件压缩和解压缩的工具类:FormatUtil.java
package com.fixbug.compress;
/**
* 文件压缩和解压缩的工具类 'A' "11000101" <=> byte
*/
public class FormatUtil {
/**
* 把字节数据data转成二进制编码串 11000101 1100010 1100010 110001 11000
* @param data
* @return
*/
public static String byteToBinaryString(byte data){
StringBuilder sbuilder = new StringBuilder();
for(int i=0; i < 8; ++i){
byte bit = (byte)(data >>> i & 1);
sbuilder.append(bit);//
}
sbuilder.reverse();//反转
return sbuilder.toString();
}
/**
* 把8个位的字符串编码转成字节 "00010100"
* @param code
* @return
*/
public static byte binaryStringToByte(String code){
byte data = 0;
for(int i=0; i < code.length(); ++i){
if(code.charAt(i) == '1'){
data += Math.pow(2, code.length()-i-1);
}
}
return data;
}
public static void main(String[] args) {
System.out.println(byteToBinaryString((byte)20));
System.out.println(binaryStringToByte("00010100"));
}
}
编码:
package com.fixbug.compress;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
/**
* 文件压缩类 通过构建文件内容的哈夫曼编码进行文件数据压缩
*/
public class FileCompress {
// 存储文件内容的频率
private Map<Character, Integer> countMap = new HashMap<>();
// 小根堆
private PriorityQueue<Entry> queue = new PriorityQueue<Entry>((a, b)->{return a.getCount().compareTo(b.getCount());});
// 指向哈夫曼树的根
private Entry root;
// 存储字符的哈夫曼编码
private Map<Character, String> codeMap = new HashMap<>();
/**
* 压缩文件并输出压缩后的文件
* @param file
*/
public void compress(File file) throws Exception{
// 1. 读取文件的每一个字节数,统计它们的频率
FileInputStream in = new FileInputStream(file);
int data = -1;
while(-1 != (data = in.read())){
Integer count = countMap.get((char)data);
if(count == null){
countMap.put((char)data, 1);
} else {
countMap.put((char)data, count+1);
}
}
in.close();
// 2.生成权值节点,放入小根堆,构建哈夫曼树
countMap.forEach((key, value)->{
Entry entry = new Entry(key, value);
queue.offer(entry);
});
while (queue.size() > 1){
Entry entry1 = queue.poll();
Entry entry2 = queue.poll();
Entry entry = new Entry('\u0000', entry1.getCount() + entry2.getCount());
entry.setLeft(entry1);
entry.setRight(entry2);
queue.offer(entry);
}
this.root = queue.poll();
// 3.获取一下叶子节点(每一个字符)的哈夫曼编码
String huffmanCode = "";
getCode(this.root, huffmanCode);
// 4.重新读取文件,获取相应字符的哈夫曼编码,进行压缩存储
StringBuilder sbuilder = new StringBuilder();
FileInputStream filein = new FileInputStream(file);
DataOutputStream fileout = new DataOutputStream(new FileOutputStream(file.getName() + ".compress"));
// 写文件头,把文件字符以及频率进行存储,用于解压缩
fileout.write(countMap.keySet().size()); // 先写文件,记录字符的个数
countMap.forEach((ch, count)->{
try {
fileout.write(ch); // 写字符内容
fileout.writeInt(count); // 写字符频率
} catch (IOException e) {
e.printStackTrace();
}
});
// 写文件具体的编码内容
while(-1 != (data = filein.read())){
String code = codeMap.get((char)data);
sbuilder.append(code);
while(sbuilder.length() >= 8){
code = sbuilder.substring(0, 8);
byte writeData = FormatUtil.binaryStringToByte(code);
fileout.write(writeData);
sbuilder.delete(0, 8);
}
}
// sbuilderk可能还有剩余的编码 11010100 1100 => 1100 0000 4
if(sbuilder.length() > 0){
byte writeData = FormatUtil.binaryStringToByte(sbuilder.toString()); // 1100 0000
writeData <<= (8 - sbuilder.length());
fileout.write(writeData);
fileout.write(sbuilder.length());
}
filein.close();
fileout.close();
}
/**
* 获取叶子节点的哈夫曼编码
* @param root
* @param huffmanCode
*/
private void getCode(Entry root, String huffmanCode) {
if(root.getLeft() == null && root.getRight() == null){
codeMap.put(root.getCh(), huffmanCode);
return;
}
getCode(root.getLeft(), huffmanCode+"0");
getCode(root.getRight(), huffmanCode+"1");
}
public static void main(String[] args) throws Exception{
FileCompress fc = new FileCompress();
fc.compress(new File("D:\\代码\\Java Code\\20171107\\com\\sun\\corba\\se\\PortableActivationIDL\\_ActivatorStub.java"));
}
}
解码:
package com.fixbug.compress;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
/**
* 实现文件的解压缩操作
*/
public class FileDecompress {
// 存储文件内容的频率
private Map<Character, Integer> countMap = new HashMap<>();
// 小根堆
private PriorityQueue<Entry> queue = new PriorityQueue<Entry>((a, b)->{return a.getCount().compareTo(b.getCount());});
// 指向哈夫曼树的根
private Entry root;
// 存储字符的哈夫曼编码
private Map<Character, String> codeMap = new HashMap<>();
/**
* 文件的解压缩操作
* @param file
*/
public void decompress(File file) throws Exception{
// 1.先读取文件头,获取字符以及频率存储到countMap中
DataInputStream in = new DataInputStream(new FileInputStream(file));
// 文件的第一个字节放的是字符的个数
int size = in.read();
// 开始读取字符以及频率
for (int i = 0; i < size; i++) {
Character ch = (char)in.read();
Integer count = in.readInt();
countMap.put(ch, count);
}
// 2.构建哈夫曼树
// 2.生成权值节点,放入小根堆,构建哈夫曼树
countMap.forEach((key, value)->{
Entry entry = new Entry(key, value);
queue.offer(entry);
});
while (queue.size() > 1){
Entry entry1 = queue.poll();
Entry entry2 = queue.poll();
Entry entry = new Entry('\u0000', entry1.getCount() + entry2.getCount());
entry.setLeft(entry1);
entry.setRight(entry2);
queue.offer(entry);
}
this.root = queue.poll();
// 3.获取一下叶子节点(每一个字符)的哈夫曼编码
String huffmanCode = "";
getCode(this.root, huffmanCode);
// 继续读取文件,然后解压缩内容,还原文件
String fileName = file.getName();
int index = fileName.lastIndexOf('.');
fileName = fileName.substring(0, index);
FileOutputStream out = new FileOutputStream(fileName);
Entry cur = this.root;
int data = -1;
int length = 0;
// 压缩文件的最后两个字节需要单独处理一下, 1100 0000 4
while(-1 != (data = in.read())){
String code = FormatUtil.byteToBinaryString((byte)data);
length = code.length();
if(in.available() == 1){ // 文件就剩最后一个字节了
length = in.read(); // 读取最后一个字节,表示最后一个编码有效的哈夫曼编码的位数
}
for(int i=0; i < length; ++i){
if(code.charAt(i) == '0'){
cur = cur.getLeft();
} else {
cur = cur.getRight();
}
if(cur.getLeft() == null && cur.getRight() == null){
out.write(cur.getCh());
cur = this.root;
}
}
}
out.close();
in.close();
}
/**
* 获取叶子节点的哈夫曼编码
* @param root
* @param huffmanCode
*/
private void getCode(Entry root, String huffmanCode) {
if(root.getLeft() == null && root.getRight() == null){
codeMap.put(root.getCh(), huffmanCode);
return;
}
getCode(root.getLeft(), huffmanCode+"0");
getCode(root.getRight(), huffmanCode+"1");
}
public static void main(String[] args) throws Exception{
FileDecompress fd = new FileDecompress();
fd.decompress(new File("_ActivatorStub.java.compress"));
}
}
标签:编码,哈夫曼,str,Entry,root,public
From: https://blog.csdn.net/2301_78353179/article/details/144918869