哈夫曼树(最优二叉树)
视频教程
哈夫曼编码是现代压缩算法的基础,是一种前缀码,即任意一个字符的二进制编码都不是其他字符的前缀
一.算法步骤与特点总结
①以权值作为根节点构建n棵二叉树,组成森林
②在森林中选出根据节点最小的两棵树合并作为一个新树的左右子树,新树的根节点为其左右子树的根节点之和
③从森林中删除刚才选取的两棵树并将新树加入森林
④重复上述步骤直到只剩下一棵树,该树便是哈夫曼树(最优二叉树)
带权路径长度即WPL
二.代码实现
第一步:定义节点
//定义节点
class Node
{
int weight;//节点的权重
int r_link;//右子节点
int l_link;//左子节点
int p_link;//父节点
char c;
public Node()
{
this.l_link=0;
this.p_link=0;
this.r_link=0;
}
//初始化
public Node(int weight,char c)
{
this.l_link=0;
this.p_link=0;
this.r_link=0;
this.weight=weight;
this.c = c;
}
}
第二步:构建哈夫曼树并实现功能
//构建哈夫曼树
class Solution {
//采用静态三叉链表
Node node[];
char strs[];
int rates[];
int min_index,second_min;
Map<Character,String> map ;//用来存储字符对应的二进制串
//strs包含了所有出现字符,rates表示每一个字符出现的频率
public Solution(char[] strs,int rates[])
{
this.rates = rates;
this.strs = strs;
//这里我们使用索引为1-2*strs.length-1的节点
this.node=new Node[2*strs.length];
for(int i=1;i<2*strs.length;i++)
{
if(i<=strs.length)//前n个字符将字符与权重存进去
this.node[i] = new Node(rates[i-1],strs[i-1]);
else//后n个字符用下面的
this.node[i]=new Node();
}
}
//构建haffunman树
private void BuildHaffuman()
{
for(int i = strs.length+1;i<=2*strs.length-1;i++)
{
//选择最小的与次小的
select(i-1);
node[min_index].p_link=i;
node[second_min].p_link=i;
node[i].r_link=min_index;
node[i].l_link=second_min;
node[i].weight=node[min_index].weight+node[second_min].weight;
}
}
//传入参数表示从1到N节点的权值最小的两个树
void select(int N)
{
int min_weight,second_min_weight;
int index=1;
//寻找索引最小的两个树
while(node[index].p_link!=0)
index++;
min_index=index;
min_weight=node[min_index].weight;
index++;
while(node[index].p_link!=0)
index++;
second_min=index;
second_min_weight=node[second_min].weight;
//将找到的最小的结果比较,如果犯了则交换
if(second_min_weight<min_weight)
{
int temp=second_min_weight;
second_min_weight=min_weight;
min_weight=temp;
temp=min_index;
min_index=second_min;
second_min=temp;
}
//继续往后找
for(int i=index+1;i<=N;i++)
{
if(node[i].p_link==0)
{
if(node[i].weight<=min_weight)//注意等号
{
second_min_weight = min_weight;
second_min=min_index;
min_weight=node[i].weight;
min_index=i;
}
else if(node[i].weight>min_weight&&node[i].weight<second_min_weight)
{
second_min_weight=node[i].weight;
second_min = i;
}
}
}
}
//编码并返回一个map保存
public Map<Character,String> encodeStr()
{
Map<Character,String> map = new HashMap<>();
BuildHaffuman();
for(int i=0;i<strs.length;i++)
{
map.put(strs[i],getStringOfChar(i+1));
}
this.map=map;//这里我做了两道,既全局map保存,也返回
return map;
}
//从对应的索引开始往上走直到树顶,对于左节点编码0,右节点编码1
//实际上index的范围为1-N,因为前N个节点对应了字符
private String getStringOfChar(int index)
{
String s ="";
int p_link=node[index].p_link;
while(p_link!=0)
{
if(node[p_link].l_link==index)
s+="0";
else
s+="1";
index = p_link;
p_link = node[p_link].p_link;
}
return new StringBuffer(s).reverse().toString();//这里需要反转一下
}
//解码
public String decodeStr(String s)
{
String str="";
for(char c : s.toCharArray())
{
str+=map.get(c);
}
return str;
}
}
第三步:测试
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
public class Math {
@Test
public void Test() {
char strs[]=new char[]{'a','b','c','d'};
int rates[]=new int[]{1,3,5,2};
Solution s = new Solution(strs,rates);
Map<Character, String> characterStringMap = s.encodeStr();
System.out.println(characterStringMap);
System.out.println(s.decodeStr("abccd"));
}
}