Huffman编码与解码

  1. 什么是哈夫曼树?
    哈夫曼树是一个完全二叉树,每一叶子节点都有自己的权重,但采用哈夫曼树来携带这些叶子节点时,它是可以达到 叶子节点的权重*叶子节点的深度 是最小的二叉树
  2. 哈夫曼树的用途:
    压缩数据
    例如当进行字符编码时,如果采用utf-8、GBK等进行数据编码,那每一个字符所使用的字节数都是相同的,例如utf-8要么是一个字节要么是两个字节,如果采用哈夫曼编码,使用二叉树的左右分支来存储0、1,可保证每一个叶子节点的编码都不相同,根据一个字符串中每一个字符出现的频率的不同,出现频率越低的字符所在的叶子节点深度越深,查找时花的时间也就相对多一点,出现频率高的叶子节点深度越小,查找是花的时间也就越少,因此使用哈夫曼树编码的时间 字符出现的频率 * 深度 也就最少
  3. 怎么构造哈夫曼树呢?
    ex:“afcacfssaacfaa” (Java版存储结构:链式存储)
  1. 根据每个字符出现的频率进行排序
    Huffman编码与解码_第1张图片

2)按顺序将两个最小的节点作为叶子节点,小的节点作为左节点,大的作为有节点,相应编码的时候往左为0,往右为1

Huffman编码与解码_第2张图片

继续进行下去

package Data.HuffTree.structure;

import java.util.*;

public class testHuffTree{
    HuffCodeTree tree = new HuffCodeTree();

    public static void main(String[] args){
        String str = "cwdav casdc csadcadav ";
        testHuffTree huffcode = new testHuffTree();
        long start = System.currentTimeMillis();
        String encode = huffcode.encode(str);
        System.out.println(System.currentTimeMillis() - start);
        System.out.println(encode);
        System.out.println(huffcode.decode(encode));
    }

    public String encode(String str) {
        char[] chars = str.toCharArray();

        // 首先整理一遍所有字符 统计他们出现的频率 也就是个字节点的权重
        Map charsInfoMap = countCharFrequence(chars);

        // 根据权重由小到大的顺序排列各结点 采用用优先级队列
        PriorityQueue nodeQue = arrangeByFre(charsInfoMap);

        // 通过这些节点 来创建哈夫曼树  得到所有字符的编码信息
        buildHuffCodeTree(nodeQue);

        //使用对所有的字符进行编码
        return this.tree.encode(chars);
    }

    public String decode(String str){
        return this.tree.decode(str);
    }

    public Map countCharFrequence(char[] chars){
        // 用键值对来存放字符以及其出现的频率
        Map charsInfoMap = new Hashtable();
        for (char c : chars) {
            Character cs = new Character(c);
            if(charsInfoMap.containsKey(cs)) {
                charsInfoMap.put(cs, charsInfoMap.get(cs) + 1);
            } else {
                charsInfoMap.put(cs, new Integer(1));
            }
        }
    }

    public PriorityQueue arrangeByFre(Map charsInfoMap){
        //通过优先队列 有序存放节点   当然节点必须重写compareTo方法
        PriorityQueue nodeQue= new PriorityQueue<>(charsInfoMap.size());
        for (Character key : charsInfoMap.keySet()) {
            Node node = new Node(key.toString(), charsInfoMap.get(key));
            nodeQue.add(node);
            this.tree.addleaf(node);
        }
        return nodeQue;
    }

    public void buildHuffCodeTree(PriorityQueue nodeQue){
        if(nodeQue == null || nodeQue.size() == 0){
            return;
        }

        //构造哈夫曼树 小的节点为左子树 大的节点为右
        while(nodeQue.size() > 1){
            Node node1 = nodeQue.poll();
            Node node2 = nodeQue.poll();
            Node parent = new Node(node1.chars + node2.chars,
                node1.frequence + node2.frequence);
            parent.leftChild = node1;
            parent.rightChild = node2;

            node1.parentChild = parent;
            node2.parentChild = parent;

            nodeQue.add(parent);
        }
        this.tree.root = nodeQue.poll();
    }
}

class HuffCodeTree{
    Node root = null;
    /**
     * 所有叶子节点 即所有进行编码的字符所在的节点
     * */
    public List leaves = new LinkedList<>();

    public String encode(char[] chars){
        if(chars.equals("")){return "";}

        // 对所有出现的字符进行编码
        Map chara_encoding = new HashMap<>();
        for(Node leaf : this.leaves){
            Node node = leaf;
            String charcode = "";
            while(node != root) {
                if (node.isLeftChild()) {
                    charcode = '0' + charcode;
                } else {
                    charcode = '1' + charcode;
                }
                node = node.parentChild;
            }
//            System.out.print(leaf.chars+ " : " + charcode + "  ");
            chara_encoding.put(leaf.chars.charAt(0), charcode);
        }

        StringBuffer code = new StringBuffer();
        for(char c : chars){
            code.append(chara_encoding.get(c));
        }
        return code.toString();
    }

    public void addleaf(Node e){
        Node node = e;
        this.leaves.add(node);
    }

    public void preOrderTra(){
        preOrderTra(root);
    }

    //递归 先序遍历
    public void preOrderTra(Node localRoot) {
        if(localRoot != null) {
            localRoot.displayNode();
            preOrderTra(localRoot.leftChild);
            preOrderTra(localRoot.rightChild);
        }
    }

    public String decode(String str){
        if(root == null){
            return null;
        }
        String deocodeString = "";
        Node node = root;
        for(char c : str.toCharArray()){
            if(node.leftChild == null && node.rightChild == null){
                deocodeString += node.chars;
                node = root;
            }
            if(c == '0'){
                node = node.leftChild;
            }else if(c == '1') {
                node = node.rightChild;
            }
        }
        return deocodeString;
    }
}

class Node implements Comparable{
    public String chars = "";
    public int frequence;
    public Node leftChild;
    public Node rightChild;
    public Node parentChild;

    public Node(String str, int fre){
        this.chars = str;
        this.frequence = fre;
    }

    public void displayNode(){
        System.out.println("节点: " + chars + " " + frequence);
    }
    public boolean isLeftChild(){
        return this.parentChild != null && this.parentChild.leftChild == this;
    }

    @Override
    public int compareTo(Node o) {
        return this.frequence - o.frequence;
    }
}

对编码与解码的思路是通过参考如下网页的,只是自己将不同部分的功能分配到了不同的类中,对集合的使用也是参照的,其使用和自己对代码的修正,大大的调整了编码的效率。

参考:https://blog.csdn.net/kimylrong/article/details/17022319

  1. C版(顺序表存储)

输入:需编码的n个字符以及对应的权重
得到:字符对应的01编码

思路:
* 使用【2 * n - 1】个节点数组,存放树中所有节点,前n个是叶子节点,也即对应各个字符。
* 每个节点保存的信息:父节点,左叶子节点,右叶子节点,权重
* 没有父节点的视作当前字数根节点,没有叶子节点位即为编码字符对应的一个节点,可知当树完全构造成功时,数组最后一个节点即为树的根节点
* 通过数组保存节点,且在构造哈夫曼树时,不断通过寻找数组中前两个既没有父节点又是最小权重的两个节点,构造下一个子树,会发现因为每次权重最小的节点总是最接近数组最前面,因此保证最后构造的哈夫曼树时最矮的(或者说是最满的)

// CHuffman.h
#include 
#include 
using namespace std;

class CHuffman
{
private:
	typedef struct {
		int weight;
		int parent;
		int lchild;
		int rchild;
	}HuffmanNode, *HuffmanTree;
	HuffmanNode* nodes;
	string* codes;
	int nodeNum;
	string* chars;
public:
	CHuffman();    // 编码的字符, 字符的权重
	~CHuffman();
	void PrintAllCodes();
	void Encode(string codes[], int a[], int n);
	string Decode(string s);
private:
	void CreateHuffTree(int a[], int n);      // 构造哈夫曼树
	void InitNode(int i, int weight = 0, int parent = -1, int lchild = -1, int rchild = -1); // 初始化一个节点
	void Select(HuffmanNode* noeds, int range, int& n1, int& n2);   // 选出权重最小的两个节点的序号
	void GetHuffcodes();    // 计算所有的0, 1哈夫曼编码
};

// CHuffman.cpp
#include "CHuffman.h"

CHuffman::CHuffman() {
	nodes = NULL;
	nodeNum = NULL;
	chars = NULL;
}

void CHuffman::Encode(string codes[], int a[], int n) {
	chars = codes;
	CreateHuffTree(a, n);
	GetHuffcodes();
}

void CHuffman::CreateHuffTree(int a[], int n) {
	// 使用数组存放整个树的节点, 共有2 * n - 1  个节点
	nodeNum = n;
	int m = 2 * n - 1;     
	nodes = new HuffmanNode[m];

	// 前n个节点存放叶子节点
	for (int i = 0; i < n; i++) {
		InitNode(i, a[i]);
	}

	for (int i = n; i < m; i++) {
		InitNode(i, 0, -1);
	}

	// 构造Huffman树
	for (int i = n; i < m; i++) {
		int n1, n2;     // 当前无父节点的所有节点中,权重最小的两个节点的序号
		Select(nodes, i, n1, n2);
		nodes[n1].parent = i;
		nodes[n2].parent = i;
		InitNode(i, nodes[n1].weight + nodes[n2].weight, -1, n1, n2);
	}
}

void CHuffman::GetHuffcodes() {
	int n = nodeNum;
	codes = new string[n];
	for (int i = 0; i < n; i++) {
		int cur = i;
		codes[i] = "";
		int parent;
		while ((parent = nodes[cur].parent) != -1) {
			cout << cur << " parent" << parent << ": " << nodes[parent].lchild << " " << nodes[parent].rchild << " \n";
			if (nodes[parent].lchild == cur) {
				codes[i] = '0' + codes[i];
			}
			else {
				codes[i] = '1' + codes[i];
			}
			cur = parent;
		}
	}
}

void CHuffman::InitNode(int i, int weight, int parent, int lchild, int rchild)
{
	nodes[i].weight = weight;
	nodes[i].parent = parent;
	nodes[i].lchild = lchild;
	nodes[i].rchild = rchild;
}

void CHuffman::Select(HuffmanNode* noeds, int range, int& n1, int& n2) {
	// 找到两个没有父节点的节点位置
	n1 = range, n2 = range;

	// 找到最小权重节点
	for (int i = 0; i < range; i++) {
		if (nodes[i].parent == -1 ){
			if (n1 == range) {
				n1 = i;
			}
			else {
				if (nodes[i].weight < nodes[n1].weight) {
					n1 = i;
				}
			}
		}
	}

	// 找到第二小权重节点
	for (int i = 0; i < range; i++) {
		if (nodes[i].parent == -1 && i != n1){
			if (n2 == range) {
				n2 = i;
			}
			else {
				if ( nodes[i].weight < nodes[n2].weight) {
					n2 = i;
				}
			}
		}
	}
}


void CHuffman::PrintAllCodes() {
	cout << "所有的编码序列为:\n";
	for (int i = 0; i < nodeNum; i++) {
		cout << chars[i] << ": " << codes[i] << "\n";
	}
}

string CHuffman::Decode(string s) {
	// 解码01串
	// 在此出直接将权重作为最后的解码值
	cout << "解码";
	int curNode = 2 * nodeNum - 2;
	if (s.length() < 1) {
		return "";
	}
	string codes = "";
	for (int i = 0; i < s.length(); i++) {
		char curChar = s.at(i);
		if (curChar == '0') {
			curNode =nodes[curNode].lchild;
		}
		else if (curChar == '1') {
			curNode = nodes[curNode].rchild;
		}
		else {
			exit;
		}
		if (nodes[curNode].lchild == -1 && nodes[curNode].rchild == -1) {
			codes += chars[curNode];
			curNode = 2 * nodeNum - 2;
		}
	}
	return codes;
}

CHuffman::~CHuffman() {
	delete[] nodes;
	delete[] codes;
	chars = nullptr;
}

// main.cpp
#include "CHuffman.h"
#include 
using namespace std;

int main()
{
	string s[] = { "a", "c", "d", "e", "f", "s" };
	int a[] = { 5, 2, 1, 1, 3, 2 };
	CHuffman tree;
	tree.Encode(s, a, 6);
	tree.PrintAllCodes();
	cout << tree.Decode("000001010110000011");
}

代码、以及代码规范还有很多问题,请查看的童鞋多多指正,谢谢!

你可能感兴趣的:(java基础,数据结构与算法)