数据结构笔记_31 哈夫曼编码(含创建哈夫曼树)

一、霍夫曼编码基本介绍:

在这里插入图片描述

二、通信领域中几种信息的处理方式:

1.定长编码:

原理是将字符对应的ASCLL码转换成二进制后传输信息。

缺点:需要存储大量的二进制数据太浪费时间,空间了。
在这里插入图片描述
比如,

空格的ASCLL码值为32,转换成二进制,如图:

在这里插入图片描述

2.变长编码:

在这里插入图片描述
缺点:匹配的时候,存在多义性。

例如,10110。既可以分解成1,0,110也可以分解成10,110亦可以是101,10.

3.哈夫曼编码:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意,这个赫夫曼树根据排序方法不同,也可能不太一样,

排序方法的不同是啥意思?比如说有可能会出现权值最小的两个二叉树的权值相加,例如:1+5等于6,而待排数列中还有好几个6,那这个1+5生成的子树该插入到这么多6的什么位置呢?

这样对应的赫夫曼编码也不完全一样,但是WPL是一样的,都是最小的,比如:如果我们让每次生成的新的二又树总是排在权值相同的二叉树的最后一个,则生成的二叉树为:
在这里插入图片描述

三、代码

1、思路:

在这里插入图片描述

扫描二维码关注公众号,回复: 12720190 查看本文章

2、分步代码

1)创建Node ,带数据(字符),权值,left 和 right.


class Node implements Comparable<Node> {
    
    
	Byte data;// 存放数据(字符)本身,比如'a' => 97 ' ' => 32
	int weight;// 权值,表示字符出现的次数
	Node left;
	Node right;

	public Node(Byte data, int weight) {
    
    
		this.data = data;
		this.weight = weight;
	}

	@Override
	public int compareTo(Node o) {
    
    
		// TODO Auto-generated method stub
		// 从小到大排序
		return this.weight - o.weight;
	}

	@Override
	public String toString() {
    
    
		return "Node [data=" + data + ", weight=" + weight + "]";
	}

	// 前序遍历
	public void preOrder() {
    
    
		System.out.println(this);
		if (this.left != null) {
    
    
			this.left.preOrder();
		}
		if (this.right != null) {
    
    
			this.right.preOrder();
		}
	}

}

2)得到"i like like like java do you like a java",对应的 byte[] 数组。

		String content = "i like like like java do you like a java";
		byte[] contentBytes = content.getBytes();

3)编写一个方法,将准备构建的赫夫曼树的Node 结点放到List ,形式[Node[data=97,weight = 5],Node[data=32,weight = 9]…](a的ASCLL码值是97,空格的ASCLL码值是32.),体现(字符:出现次数)
:d:1,y:1, u:1,,j:2,v:2,o:2,l:4,k:4,e:4,i:5,a:5,空格:9

	/**
	 * @param bytes 接收一个字节数组
	 * @return 返回的就是List形式[Node[data=97,weight = 5],Node[data=32,weight = 9]......]
	 */
		private static List<Node> getNodes(byte[] bytes) {
    
    
		// 1、创建一个ArrayList
		ArrayList<Node> nodes = new ArrayList<Node>();
		// 遍历 bytes,统计 每一个byte出现的次数 -> map[key,value]
		Map<Byte, Integer> counts = new HashMap<>();// 所谓映射,即给一个人编了个号,提到号就能知道是哪个人。
		// Map集合将键映射到值的对象,一个映射不能包含重复的键,每个键最多只能映射到一个值,
		// Map接口和Collection接口的不同,Map是双列的,Collection是单列的.
		for (byte b : bytes) {
    
    
			Integer count = counts.get(b);
			if (count == null) {
    
    // Map还没有这个字符数据,第一次
				counts.put(b, 1);
			} else {
    
    
				counts.put(b, count + 1);
			}
		}
		// 把每一个键值对,转成一个Node对象,并加入到nodes集合
		// 遍历map 集合

		for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
    
    
			// for each循环,定义一个变量entry暂存集合中的每一个元素。循环counts.entrySet()中的每一个元素。
			// 其中,entrySet()表示返回Map.Entry对象(映射中的键/值对)的一个集视图(集合视图就是把集合
			// 里面的东西给你展示出来, 仅供查看, 而不能修改)。可以从这个集中删除元素,
			// 它们将从映射中删除,但是不能添加任何元素。

			// Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。
			// 它表示Map中的一个实体(一个key-value对)。接口中有getKey(),getValue方法。
			nodes.add(new Node(entry.getKey(), entry.getValue()));
			// getKey()、getValue()表示返回这个映射条目的键或值
		}
		return nodes;
	}

4)通过List 创建对应的赫夫曼树。

	private static Node createHuffmanTree(List<Node> nodes) {
    
    
		while (nodes.size() > 1) {
    
    
			// 排序,从小到大
			Collections.sort(nodes);
			// 取出第一颗最小的二叉树
			Node leftNode = nodes.get(0);
			// 取出第二颗最小的二叉树
			Node rightNode = nodes.get(1);
			// 创建一颗新的二叉树,它的根节点 没有data ,只有权值
			Node parent = new Node(null, leftNode.weight + rightNode.weight);
			parent.left = leftNode;
			parent.right = rightNode;

			// 将已经处理的两颗二叉树从nodes删除
			nodes.remove(leftNode);
			nodes.remove(rightNode);
			// 将新的二叉树,加入到nodes
			nodes.add(parent);
		}
		// nodes 最后的结点,就是哈夫曼树的根节点
		return nodes.get(0);
	}

3、完整代码:

package com.huey.huffmancode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HuffmanCode {
    
    

	public static void main(String[] args) {
    
    
		// TODO Auto-generated method stub
		String content = "i like like like java do you like a java";
		byte[] contentBytes = content.getBytes();
		System.out.println(contentBytes.length);

		List<Node> nodes = getNodes(contentBytes);
		System.out.println("nodes=" + nodes);

		// 测试创建的二叉树
		System.out.println("赫夫曼树");
		Node huffmanTreeRoot = createHuffmanTree(nodes);
		System.out.println("前序遍历");
		preOrder(huffmanTreeRoot);

	}

	public static void preOrder(Node root) {
    
    
		if (root != null) {
    
    
			root.preOrder();
		} else {
    
    
			System.out.println("赫夫曼树为空");
		}
	}

	/**
	 * @param bytes 接收一个字节数组
	 * @return 返回的就是List形式[Node[data=97,weight = 5],Node[data=32,weight = 9]......]
	 */
	private static List<Node> getNodes(byte[] bytes) {
    
    
		// 1、创建一个ArrayList
		ArrayList<Node> nodes = new ArrayList<Node>();
		// 遍历 bytes,统计 每一个byte出现的次数 -> map[key,value]
		Map<Byte, Integer> counts = new HashMap<>();
		for (byte b : bytes) {
    
    
			Integer count = counts.get(b);
			if (count == null) {
    
    // Map还没有这个字符数据,第一次
				counts.put(b, 1);
			} else {
    
    
				counts.put(b, count + 1);
			}
		}
		// 把每一个键值对,转成一个Node对象,并加入到nodes集合
		// 遍历map 集合

		for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
    
    
			// for each循环,定义一个变量entry暂存集合中的每一个元素。循环counts.entrySet()中的一个元素。

			// Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。
			// 它表示Map中的一个实体(一个key-value对)。接口中有getKey(),getValue方法。
			nodes.add(new Node(entry.getKey(), entry.getValue()));
			// getKey()、getValue()表示返回这个映射条目的键或值
		}
		return nodes;
	}

	// 可以通过List,创建对应的赫夫曼树
	private static Node createHuffmanTree(List<Node> nodes) {
    
    
		while (nodes.size() > 1) {
    
    
			// 排序,从小到大
			Collections.sort(nodes);
			// 取出第一颗最小的二叉树
			Node leftNode = nodes.get(0);
			// 取出第二颗最小的二叉树
			Node rightNode = nodes.get(1);
			// 创建一颗新的二叉树,它的根节点 没有data ,只有权值
			Node parent = new Node(null, leftNode.weight + rightNode.weight);
			parent.left = leftNode;
			parent.right = rightNode;

			// 将已经处理的两颗二叉树从nodes删除
			nodes.remove(leftNode);
			nodes.remove(rightNode);
			// 将新的二叉树,加入到nodes
			nodes.add(parent);
		}
		// nodes 最后的结点,就是哈夫曼树的根节点
		return nodes.get(0);
	}

}

//创建Node ,带数据和权值
class Node implements Comparable<Node> {
    
    
	Byte data;// 存放数据(字符)本身,比如'a' => 97 ' ' => 32
	int weight;// 权值,表示字符出现的次数
	Node left;
	Node right;

	public Node(Byte data, int weight) {
    
    
		this.data = data;
		this.weight = weight;
	}

	@Override
	public int compareTo(Node o) {
    
    
		// TODO Auto-generated method stub
		// 从小到大排序
		return this.weight - o.weight;
	}

	@Override
	public String toString() {
    
    
		return "Node [data=" + data + ", weight=" + weight + "]";
	}

	// 前序遍历
	public void preOrder() {
    
    
		System.out.println(this);
		if (this.left != null) {
    
    
			this.left.preOrder();
		}
		if (this.right != null) {
    
    
			this.right.preOrder();
		}
	}

}

4、测试

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_45909299/article/details/114338748