赫夫曼編碼(壓縮解壓字符串第二版)


package com.qyx;

import java.lang.reflect.Array;
import java.util.*;
/**
 * 赫夫曼編碼
 */
public class HuffmanCode {
  public static void main(java.lang.String[] args)
  {
      java.lang.String str="i like java forever";
      byte[] bytes=str.getBytes();
      byte[] bys=huffmanZip(bytes);
      System.out.println(Arrays.toString(bys));
      byte[] bytes1=decode(huffmanCodes,bys);
      System.out.println(new String(bytes1));
  }
    private static List<Node> getNodes(byte[] bytes)
    {
        //1 創建ArrayList
        ArrayList<Node> list=new ArrayList<Node>();
        //遍歷bytes,統計存儲每個byte出現的次數->map
        Map<Byte,Integer> counts=new HashMap<Byte, Integer>();
        for(byte b:bytes)
        {
            Integer count=counts.get(b);
            if (count==null)
            {
                counts.put(b,1);
            }else {
                counts.put(b, ++count);
            }
        }
        //把每個鍵值對轉化成一個Node對象,並放入到nodes集合中
        for (Map.Entry<Byte,Integer> entry:counts.entrySet())
        {
            list.add(new Node(entry.getKey(),entry.getValue()));
        }
        return list;
    }
    //通過List創建對應的哈夫曼樹
    private static Node createHuffManTree(List<Node> list)
    {
        while (list.size()>1)
        {
            //排序,從小到大,根據我們實現的compareTo方法來決定的
            Collections.sort(list);
            Node leftNode=list.get(0);
            Node rightNode=list.get(1);
            //新的根節點沒有data,只有權值
            Node parent=new Node(null,leftNode.weight+rightNode.weight);
            parent.left=leftNode;
            parent.right=rightNode;
            //將已經處理的兩顆二叉樹從list移除
            list.remove(leftNode);
            list.remove(rightNode);
            list.add(parent);
        }
        return list.get(0);
    }
    //生成哈夫曼樹對應的哈夫曼編碼表

    /**
     * 思路:
     * 1 將赫夫曼編碼表存放在Map<Byte,String>中
     * 2 在生成赫夫曼編碼表示,需要去拼接路徑,定義一個StringBuilder
     * 存儲某個葉子節點的路徑
     * @param
     */
    private static Map<Byte,String> huffmanCodes=new HashMap<Byte, String>();
    private static StringBuilder builder=new StringBuilder();

    /**
     * 功能:將傳入的node結點的所有葉子節點的赫夫曼編碼得到,並放入到huffmanCode的集合中
     * @param node 傳入節點
     * @param code 路徑:左子節點是0,右子節點是1
     * @param builder 用於拼接路徑
     */
    private static void getCodes(Node node,String code,StringBuilder builder)
    {
        StringBuilder builder1=new StringBuilder(builder);
        //將code加入到builder1
        builder1.append(code);
        if (node!=null)
        {
            //判斷當前node是葉子節點還是非葉子節點
            if(node.data==null)
            {
                //非葉子節點,則需要遞歸
                //向左遞歸
                getCodes(node.left,"0",builder1);
                //向右遞歸
                getCodes(node.right,"1",builder1);
            }else{
                //說明是一個葉子節點
                huffmanCodes.put(node.data,builder1.toString());
            }
        }
    }
    //為了調用方便,重載getCodes
    private static Map<Byte,String> getCodes(Node root)
    {
        if (root==null)
        {
            return null;
        }
        //處理root的左子樹
        getCodes(root.left,"0",builder);
        //處理root的右子樹
        getCodes(root.right,"1",builder);
        return huffmanCodes;
    }
    //編寫一個方法,將字符串對應的byte[]數組,通過生成的赫夫曼編碼表,返回一個赫夫曼編碼 壓縮后的byte[]

    /**
     *
     * @param bytes 這是原始的字符串生成byte數組
     * @param huffmanCodes 這是字符對應的赫夫曼編碼的map
     * @return  返回赫夫曼編碼處理后的字符數組
     */
    private static byte[] zip(byte[] bytes,Map<Byte,String>huffmanCodes)
    {
        //1 利用huffmanCodes將bytes轉成轉成赫夫曼編碼對應的字符串
        StringBuilder stringBuilder=new StringBuilder();
        //遍歷bytes數組
        for(byte b:bytes)
        {
            stringBuilder.append(huffmanCodes.get(b));
        }
        //System.out.println("測試stringBuilder="+stringBuilder.toString());
        //將赫夫曼字符串轉成byte[]
        int len;
        if (stringBuilder.length()%8==0)
        {
            len=stringBuilder.length()/8;
        }else {
            len=stringBuilder.length()/8+1;
        }
        //創建 存儲壓縮后的byte數組
        byte[] by=new byte[len];
        int index=0;//記錄是第幾個byte
        for (int i=0;i<stringBuilder.length();i+=8)
        {
            //因為是每8位對應一個byte,所有步長+8
            String strByte;
            if (i+8>stringBuilder.length())
            {
                //不夠8位
                strByte=stringBuilder.substring(i,stringBuilder.length());
            }else {
                strByte = stringBuilder.substring(i, i + 8);
            }
            //將strByte轉成byte數組,放入到by
            by[index]= (byte) Integer.parseInt(strByte,2);
            index++;
        }
        return by;
    }
    //使用一個方法,將前面的方法封裝起來,便於調用

    /**
     *
     * @param bytes 原始的字符串對應的字節數組
     * @return 經歷赫夫曼編碼處理的字節數組
     */
    private static byte[] huffmanZip(byte[] bytes)
    {
        List<Node> nodes=getNodes(bytes);
        //創建赫夫曼樹
        Node root=createHuffManTree(nodes);
        //生成對應的赫夫曼編碼(根據赫夫曼樹)
        Map<Byte,String> huffmanCodes=getCodes(root);
        //根據赫夫曼編碼對原始的數組進行壓縮
        byte[] huffmanCodeBytes=zip(bytes,huffmanCodes);
        return huffmanCodeBytes;
    }

    /**
     * @param flag 是否需要補位
     * @param b 需要轉換的byte
     * @return 二進制后的補碼
     */
    private static String byteToBitString(boolean flag,byte b)
    {
        int temp=b;
        if (flag){
            temp|=256;
        }
        String str=Integer.toBinaryString(temp);
        if (flag){
            return str.substring(str.length()-8);
        }else {
            return str;
        }
    }

    /**
     * 編寫一個方法,完成對壓縮數據的解碼
     * @param huffmanCodes 赫夫曼編碼表 map
     * @param bytes 赫夫曼編碼得到的字節數組
     * @return 返回的就是原來字符串對應的數組
     */
    private static byte[] decode(Map<Byte,String> huffmanCodes,byte[] bytes)
    {
        //1 先得到赫夫曼編碼對應的二進制的字符串,刑如1010100010111...
        StringBuilder builder=new StringBuilder();
        //將Byte數組轉成二進制的字符串
        for (int i=0;i<bytes.length;i++)
        {
            //首先需要判斷是否是最后一個字節
            boolean flag=(i==bytes.length);
            byte b=bytes[i];
            builder.append(byteToBitString(!flag,b));
        }
        //把字符串按照指定的赫夫曼編碼進行解碼
        //把赫夫曼編碼表進行交換,因為反向查詢
        Map<String,Byte> map=new HashMap<String,Byte>();
        for (Map.Entry<Byte,String> entry:huffmanCodes.entrySet())
        {
            map.put(entry.getValue(),entry.getKey());
        }
        //創建一個集合,存放Byte
        List<Byte> list=new ArrayList<Byte>();
        System.out.println(builder.length());
        //i 可以理解成就是索引,掃描stringbuilder
        for (int i=0;i<builder.length();)
        {
            int count=1;
            boolean flag=true;
            Byte b=null;
            while (flag) {

                String key=builder.substring(i,i+count);//i不動,讓count移動,指定匹配一個字符
                b=map.get(key);
                if (b==null)
                {
                    //說明沒有匹配到
                    count++;
                }else{
                    //匹配到
                    flag=false;
                }
            }
            list.add(b);
            i+=count;//讓i 直接移動到count的位置
        }
        //當for循環結束后,我們list中就存放了所有的字符"i like java,forever"
        //把list中的數據放入byte[]數組並返回
        byte[] b=new byte[list.size()];
        for (int i=0;i<b.length;i++)
        {
            b[i]=list.get(i);
        }
        return b;
    }
    //前序遍歷
    private static void preOrder(Node root)
    {
        if (root!=null)
        {
            root.preOrder();
        }else{
            System.out.println("哈夫曼樹為空");
        }
    }
}
class Node implements Comparable<Node>{
    Byte data;//存放數據 a=97
    int weight;//權值,字符出現次數
    Node left;
    Node right;

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

    @Override
    public int compareTo(Node node) {
        return this.weight-node.weight;
    }
    //重新toString

    @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();
        }
    }
}

赫夫曼樹及赫夫曼編碼原理請見我的另一篇文章

package com.qyx;

import java.lang.reflect.Array;
import java.util.*;
/**
* 赫夫曼編碼
*/
public class HuffmanCode {
public static void main(java.lang.String[] args)
{
java.lang.String str="i like java forever";
byte[] bytes=str.getBytes();
byte[] bys=huffmanZip(bytes);
System.out.println(Arrays.toString(bys));
byte[] bytes1=decode(huffmanCodes,bys);
System.out.println(new String(bytes1));
}
private static List<Node> getNodes(byte[] bytes)
{
//1 創建ArrayList
ArrayList<Node> list=new ArrayList<Node>();
//遍歷bytes,統計存儲每個byte出現的次數->map
Map<Byte,Integer> counts=new HashMap<Byte, Integer>();
for(byte b:bytes)
{
Integer count=counts.get(b);
if (count==null)
{
counts.put(b,1);
}else {
counts.put(b, count + 1);
}
}
//把每個鍵值對轉化成一個Node對象,並放入到nodes集合中
for (Map.Entry<Byte,Integer> entry:counts.entrySet())
{
list.add(new Node(entry.getKey(),entry.getValue()));
}
return list;
}
//通過List創建對應的哈夫曼樹
private static Node createHuffManTree(List<Node> list)
{
while (list.size()>1)
{
//排序,從小到大,根據我們實現的compareTo方法來決定的
Collections.sort(list);
Node leftNode=list.get(0);
Node rightNode=list.get(1);
//新的根節點沒有data,只有權值
Node parent=new Node(null,leftNode.weight+rightNode.weight);
parent.left=leftNode;
parent.right=rightNode;
//將已經處理的兩顆二叉樹從list移除
list.remove(leftNode);
list.remove(rightNode);
list.add(parent);
}
return list.get(0);
}
//生成哈夫曼樹對應的哈夫曼編碼表

/**
* 思路:
* 1 將赫夫曼編碼表存放在Map<Byte,String>中
* 2 在生成赫夫曼編碼表示,需要去拼接路徑,定義一個StringBuilder
* 存儲某個葉子節點的路徑
* @param
*/
private static Map<Byte,String> huffmanCodes=new HashMap<Byte, String>();
private static StringBuilder builder=new StringBuilder();

/**
* 功能:將傳入的node結點的所有葉子節點的赫夫曼編碼得到,並放入到huffmanCode的集合中
* @param node 傳入節點
* @param code 路徑:左子節點是0,右子節點是1
* @param builder 用於拼接路徑
*/
private static void getCodes(Node node,String code,StringBuilder builder)
{
StringBuilder builder1=new StringBuilder(builder);
//將code加入到builder1
builder1.append(code);
if (node!=null)
{
//判斷當前node是葉子節點還是非葉子節點
if(node.data==null)
{
//非葉子節點,則需要遞歸
//向左遞歸
getCodes(node.left,"0",builder1);
//向右遞歸
getCodes(node.right,"1",builder1);
}else{
//說明是一個葉子節點
huffmanCodes.put(node.data,builder1.toString());
}
}
}
//為了調用方便,重載getCodes
private static Map<Byte,String> getCodes(Node root)
{
if (root==null)
{
return null;
}
//處理root的左子樹
getCodes(root.left,"0",builder);
//處理root的右子樹
getCodes(root.right,"1",builder);
return huffmanCodes;
}
//編寫一個方法,將字符串對應的byte[]數組,通過生成的赫夫曼編碼表,返回一個赫夫曼編碼 壓縮后的byte[]

/**
*
* @param bytes 這是原始的字符串生成byte數組
* @param huffmanCodes 這是字符對應的赫夫曼編碼的map
* @return 返回赫夫曼編碼處理后的字符數組
*/
private static byte[] zip(byte[] bytes,Map<Byte,String>huffmanCodes)
{
//1 利用huffmanCodes將bytes轉成轉成赫夫曼編碼對應的字符串
StringBuilder stringBuilder=new StringBuilder();
//遍歷bytes數組
for(byte b:bytes)
{
stringBuilder.append(huffmanCodes.get(b));
}
//System.out.println("測試stringBuilder="+stringBuilder.toString());
//將赫夫曼字符串轉成byte[]
int len;
if (stringBuilder.length()%8==0)
{
len=stringBuilder.length()/8;
}else {
len=stringBuilder.length()/8+1;
}
//創建 存儲壓縮后的byte數組
byte[] by=new byte[len];
int index=0;//記錄是第幾個byte
for (int i=0;i<stringBuilder.length();i+=8)
{
//因為是每8位對應一個byte,所有步長+8
String strByte;
if (i+8>stringBuilder.length())
{
//不夠8位
strByte=stringBuilder.substring(i);
}else {
strByte = stringBuilder.substring(i, i + 8);
}
//將strByte轉成byte數組,放入到by
by[index]= (byte) Integer.parseInt(strByte,2);
index++;
}
return by;
}
//使用一個方法,將前面的方法封裝起來,便於調用

/**
*
* @param bytes 原始的字符串對應的字節數組
* @return 經歷赫夫曼編碼處理的字節數組
*/
private static byte[] huffmanZip(byte[] bytes)
{
List<Node> nodes=getNodes(bytes);
//創建赫夫曼樹
Node root=createHuffManTree(nodes);
//生成對應的赫夫曼編碼(根據赫夫曼樹)
Map<Byte,String> huffmanCodes=getCodes(root);
//根據赫夫曼編碼對原始的數組進行壓縮
byte[] huffmanCodeBytes=zip(bytes,huffmanCodes);
return huffmanCodeBytes;
}

/**
* @param flag 是否需要補位
* @param b 需要轉換的byte
* @return 二進制后的補碼
*/
private static String byteToBitString(boolean flag,byte b)
{
int temp=b;
if (flag){
temp|=256;
}
String str=Integer.toBinaryString(temp);
if (flag){
return str.substring(str.length()-8);
}else {
return str;
}
}

/**
* 編寫一個方法,完成對壓縮數據的解碼
* @param huffmanCodes 赫夫曼編碼表 map
* @param bytes 赫夫曼編碼得到的字節數組
* @return 返回的就是原來字符串對應的數組
*/
private static byte[] decode(Map<Byte,String> huffmanCodes,byte[] bytes)
{
//1 先得到赫夫曼編碼對應的二進制的字符串,刑如1010100010111...
StringBuilder builder=new StringBuilder();
//將Byte數組轉成二進制的字符串
for (int i=0;i<bytes.length;i++)
{
//首先需要判斷是否是最后一個字節
boolean flag=(i==bytes.length);
byte b=bytes[i];
builder.append(byteToBitString(!flag,b));
}
//把字符串按照指定的赫夫曼編碼進行解碼
//把赫夫曼編碼表進行交換,因為反向查詢
Map<String,Byte> map=new HashMap<String,Byte>();
for (Map.Entry<Byte,String> entry:huffmanCodes.entrySet())
{
map.put(entry.getValue(),entry.getKey());
}
//創建一個集合,存放Byte
List<Byte> list=new ArrayList<Byte>();
System.out.println(builder.length());
//i 可以理解成就是索引,掃描stringbuilder
for (int i=0;i<builder.length();)
{
int count=1;
boolean flag=true;
Byte b=null;
while (flag) {

String key=builder.substring(i,i+count);//i不動,讓count移動,指定匹配一個字符
b=map.get(key);
if (b==null)
{
//說明沒有匹配到
count++;
}else{
//匹配到
flag=false;
}
}
list.add(b);
i+=count;//讓i 直接移動到count的位置
}
//當for循環結束后,我們list中就存放了所有的字符"i like java,forever"
//把list中的數據放入byte[]數組並返回
byte[] b=new byte[list.size()];
for (int i=0;i<b.length;i++)
{
b[i]=list.get(i);
}
return b;
}
//前序遍歷
private static void preOrder(Node root)
{
if (root!=null)
{
root.preOrder();
}else{
System.out.println("哈夫曼樹為空");
}
}
}
class Node implements Comparable<Node>{
Byte data;//存放數據 a=97
int weight;//權值,字符出現次數
Node left;
Node right;

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

@Override
public int compareTo(Node node) {
return this.weight-node.weight;
}
//重新toString

@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();
}
}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM