菜鸟笔记之数据结构(11)

数据结构与算法—树3


声明:以下是学的尚硅谷网课并结合网上资料所记的笔记。可能会有一些错误,发现了会修改。

赫夫曼树

概念:给定n个权值作为n个叶子节点,构造一颗二叉树,若该树的带权路径长度达到最小,称这样的树为最优二叉树,也叫赫夫曼树(哈夫曼树)。它是带权路径长度最短的树,权值较大的结点离根较近。

名词解释:

  1. 路径:一棵树中,从一个结点往下可以达到的子结点,或孙子结点的通路,称为路径。
  2. 路径长度:通路中分支的数目称为路径长度,根节点到第L层结点的路径长度为 L-1
  3. :树中结点的值。
  4. 结点带权路径长度:从根结点到该结点之间路径长度与该结点的权的乘积。
  5. 树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL,权值越大的结点离根结点越近的二叉树是最优二叉树,即WPL最小的就是赫夫曼树

赫夫曼树构建思路:

  1. 从小到大进行排序,每个数据都是一个结点,每个节点可以看成是一颗简单的二叉树。
  2. 取出根结点权值最小的两颗二叉树。
  3. 组成一颗新的二叉树,该新的二叉树的根结点权值是前面两颗二叉树根结点权值之和。
  4. 再将这颗新的二叉树,以根结点的权值大小再次进行排序,重复1,2,3,4步,直到数列中所有的数据都被处理,就得到一颗赫夫曼树。

例如:对于数组{13,7,8,3,29,6,1},变换过程如下:

转换图

赫夫曼编码

特点:

  • 是一种编码方式,属于一种程序算法,是无损压缩。
  • 是赫夫曼树在电讯通信中的经典应用。
  • 广泛应用于数据文件压缩,压缩率通常20%~90%之间。
  • 是可变字长编码的一种。

步骤:

  1. 先统计待压缩字符串的每个字符出现的次数。
  2. 将每个字符出现的次数表示为权值,构建赫夫曼树(字符当做每个结点的属性)。
  3. 根据赫夫曼树,给各个字符规定编码,向左的路径为0,向右的路径为1。
  4. 将字符串编码成赫夫曼编码。
    赫夫曼编码

注: 可看出赫夫曼编码为一种前缀编码方式,因为字符集中任一字符的编码都不是其他字符编码的前缀。出现频率高的字符,也就是权值大的编码比较短,出现频率低的也就是权值小的编码比较长,体现了赫夫曼树的思想,用于数据压缩。赫夫曼树根据排序方式不同,也可能不太一样(当有相同权值的字符时),这样其赫夫曼编码也不同,但WPL一样,即带权路径长度最小。

代码如下:

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

public class HuffmanCode {
   

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

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

		// 测试,创建的二叉树
		System.out.println("生成的赫夫曼树");
		Node huffmanTreeRoot = createHuffmanTree(nodes);
		HuffmanCode.preOrder(huffmanTreeRoot);

		// 测试,是否生成了对应的赫夫曼编码
		HuffmanCode.getCodes(huffmanTreeRoot);
		System.out.println("生成的赫夫曼编码表: " + huffmanCodes);
	}

	/**
	* @param contentBytes接受字节数组
	* @return 返回一个List集合,形式[Node[data=97,weight=5],Node[data=32,weight=9]...]
	*/
	private static List<Node> getNodes(byte[] contentBytes) {
   
		// 创建一个ArrayList
		ArrayList<Node> nodes = new ArrayList<Node>();
		// 遍历bytes,统计每一个byte出现的次数->map[key,value]
		Map<Byte, Integer> counts = new HashMap<Byte, Integer>();
		for (byte b : contentBytes) {
   
			Integer count = counts.get(b);
			if (count == null) {
   
				counts.put(b, 1);
			} else {
   
				counts.put(b, count + 1);
			}
		}
		// 把每一个键值对转成一个Node对象,并加入到nodes集合
		// 遍历map
		Set<Byte> keySet = counts.keySet();
		Iterator<Byte> it = keySet.iterator();
		while(it.hasNext()) {
   
			byte key = it.next();
			Integer value = counts.get(key);
			nodes.add(new Node(key,value));
		}
		return nodes;
		//另一种遍历方法
		//for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
   
		//	nodes.add(new Node(entry.getKey(), entry.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);
	}
	
	// 前序遍历的方法
	private static void preOrder(Node root) {
   
		if (root != null) {
   
			root.preOrder();
		} else {
   
			System.out.println("root节点为空!");
		}
	}
	/*
	 * 生成赫夫曼对应的赫夫曼编码 思路: 
	 * 1.将赫夫曼编码表存放到Map<Byte,String>,形式如:32(表示空格)->01, 97(表示a字符)->100等 
	 * 	{32=01,97=100, 100=11000, 117=11001, 101=1110, 118=11011, 105=101, 121=11010, 106=0010, 107=1111, 108=000, 111=0011}
	 * 2.在生成赫夫曼编码表时,需要去拼接路径,定义一个StringBuilder存储某个叶子节点的路径
	 */
	static Map<Byte
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值