构建思路
首先找到每一个字母出现的次数作为该字母的权重,然后每一次找权重最小的两个构建哈夫曼树,构建好了之后按照左0右1的原则给字符串进行编码,由此编码可以读出该字符串
构建哈夫曼树
不引用别人的博客了,咱也会了,直接上代码,代码有详细解析
代码中结果会输出每个字符的权重和编码,如果不想要,可以直接删掉
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class HFM {
public static void main(String[] args) {
String str = "ajsfhsdhfasdfkjhsdfhsalkdjsfhdsfhjsklasjfjksfdghkslkdahjfjhsdgasjfhsdjfjshhfg";
//把每一个字符和出现的次数放入哈希map中
HashMap<String,Integer> map = new HashMap<>();
for (int i = 0; i < str.length(); i++) {
//拿到当前位置的字符
String ch = str.charAt(i)+"";
if (map.get(ch) == null){
//如果这个字符在map中没有,放入map
map.put(ch,1);
}else {
//如果这个字符在map中有,拿出来把value+1再放回去覆盖原来的
map.put(ch,map.get(ch) + 1);
}
}
//把map中的数据拿出来建立结点放入数组中
ArrayList<NodeClass> arr = new ArrayList<>();
for (Map.Entry<String ,Integer> en :map.entrySet()){
//遍历map拿到每一个key和value建立一个结点放入数组中
NodeClass no = new NodeClass(en.getKey(),en.getValue());
arr.add(no);
}
//把数组中的结点拿出来建立哈夫曼树
for (;;){
if(arr.size()>1){
//拿到节点中权重最小的两个结点
NodeClass[] data = getNode(arr);
//新建一个结点把左右孩子分别指向拿到的两个结点,权重是两个结点的权重值和
NodeClass root = new NodeClass(null,data[0].num+data[1].num);
root.left = data[0];
root.right = data[1];
//把新建的结点加入数组中
arr.add(root);
}else {
//如果数组中只剩下一个结点,那么哈夫曼树构建成功,跳出循环
break;
}
}
NodeClass tree = arr.get(0);
//对哈夫曼树的进行编码输出字符串对应的编码
//编码所用的map
HashMap<String,String> charMaps = new HashMap<>();
//解码所用的map
HashMap<String,String> codeMaps = new HashMap<>();
treeShow(tree,"",charMaps,codeMaps);
String hafucode = "";
for(int i = 0; i < str.length(); i++) {
String ch = str.charAt(i) + "";
hafucode += charMaps.get(ch);
}
System.out.println( hafucode );
//把编码再重新解码输出为字符串
String reverse = "";
int end = 0;
for (int i = 0; i < hafucode.length(); ){
end++;
String tmp = hafucode.substring(i,end);
if(codeMaps.get(tmp)!=null){
reverse+=codeMaps.get(tmp);
i=end;
}
}
System.out.println(reverse);
}
public static void treeShow(NodeClass tree,String code,HashMap<String,String> charMaps,HashMap<String,String> codeMaps){
//哈夫曼树一定是一个完全二叉树,所以如果某个结点要么是叶子结点要么是左右孩子都有
if (tree.right == null){
//如果当前节点没有右孩子,说明它是叶子节点,输出一下该位置的权重,字符和字符所对应的编码
System.out.println(tree.num+"||"+tree.ch+"||"+code);
//把字符和编码放入两个map
charMaps.put(tree.ch,code);
codeMaps.put(code,tree.ch);
}else {
//如果不是叶子结点,递归调用
treeShow(tree.left,code+"0",charMaps,codeMaps);
treeShow(tree.right,code+"1",charMaps,codeMaps);
}
}
public static NodeClass[] getNode(ArrayList<NodeClass> arr){
//建立一个长度为2的数组返回
NodeClass[] returnArr= new NodeClass[2];
//新建两个下标,用来保存权重最小的两个结点的下标,其中1是最小的,2是次小的
//初始值是0位置和1位置
int index1 = 0;
int index2 = 1;
if (arr.get(0).num>arr.get(1).num){
//如果0位置的大,交换两下标的值
index1 = 1;
index2 = 0;
}
for (int i = 0; i < arr.size(); i++) {
//如果有某个结点的值比下标1位置的结点的权重小,那么把1的下标给2,然后把该位置的下标赋给1
if(arr.get(i).num < arr.get(index1).num){
index2 = index1;
index1 = i;
//如果有某个结点的权重比1位置的大但是比2位置的小,把该下标赋给2
}else if (arr.get(i).num>arr.get(index1).num && arr.get(i).num<arr.get(index2).num){
index2 = i;
}
}
//拿到两个位置的结点
returnArr[0] = arr.get(index1);
returnArr[1] = arr.get(index2);
//把两个结点在数组中删除
arr.remove(index1);
if(index2<index1){
arr.remove(index2);
}else {
arr.remove(index2-1);
}
//返回数组
return returnArr;
}
}
class NodeClass{
String ch = "";
int num;
NodeClass left;
NodeClass right;
@Override
public String toString() {
return "NodeClass{" +
"ch='" + ch + '\'' +
", num=" + num +
", left=" + left +
", right=" + right +
'}';
}
public NodeClass(String ch, int num) {
this.ch = ch;
this.num = num;
}
public NodeClass(){
}
}