首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 實現哈夫曼編碼與解碼完整實戰(含樹構建、編碼表生成、壓縮解壓)

     編輯:Java 相關知識

哈夫曼編碼核心思想

哈夫曼編碼 是一種前綴編碼,利用字符出現頻率構建最優二叉樹(哈夫曼樹),高頻字符編碼短、低頻字符編碼長,實現無損壓縮。核心步驟:

  1. 統計字符權重(頻率)
  2. 構建哈夫曼樹(貪心:反復合併最小權重節點)
  3. 生成編碼表(左 0 右 1,從葉子到根)
  4. 編碼壓縮 / 解碼還原

完整可運行實現代碼

import java.util.*;

public class HuffmanCoding {
    // 節點類:包含字符、權重、左右孩子、父節點索引
    private static class Node {
        char ch;        // 字符(葉子節點有效)
        int weight;     // 權重
        int left, right, parent; // 孩子、父節點索引
        Node(char ch, int weight) {
            this.ch = ch; this.weight = weight;
            left = right = parent = -1;
        }
        Node(int weight, int left, int right) {
            this.ch = 0; this.weight = weight;
            this.left = left; this.right = right; parent = -1;
        }
        boolean isLeaf() { return left == -1 && right == -1; }
    }

    private final Node[] tree;      // 哈夫曼樹數組
    private final String[] codes;   // 編碼表
    private final Map encodeMap = new HashMap<>();
    private final Map decodeMap = new HashMap<>();

    /** 構造器:傳入字符與權重數組 [[ch, weight], ...] */
    public HuffmanCoding(int[][] charWeights) {
        int n = charWeights.length;
        if (n == 0) throw new IllegalArgumentException("字符集不能為空");
        
        // 1. 初始化葉子節點
        tree = new Node[2 * n - 1];
        for (int i = 0; i < n; i++) {
            tree[i] = new Node((char) charWeights[i][0], charWeights[i][1]);
        }
        // 2. 構建內部節點
        for (int i = n; i < 2 * n - 1; i++) {
            // 選擇兩個權重最小且無父節點的節點
            int[] minTwo = selectMinTwo(i);
            int l = minTwo[0], r = minTwo[1];
            tree[l].parent = tree[r].parent = i;
            tree[i] = new Node(tree[l].weight + tree[r].weight, l, r);
        }
        // 3. 生成編碼表
        codes = new String[n];
        buildCodes(n);
    }

    /** 選擇權重最小的兩個無父節點 */
    private int[] selectMinTwo(int limit) {
        int min1 = -1, min2 = -1;
        for (int i = 0; i < limit; i++) {
            if (tree[i].parent == -1) { // 無父節點
                if (min1 == -1 || tree[i].weight < tree[min1].weight) {
                    min2 = min1;
                    min1 = i;
                } else if (min2 == -1 || tree[i].weight < tree[min2].weight) {
                    min2 = i;
                }
            }
        }
        return new int[]{min1, min2};
    }

    /** 從葉子向根構建編碼 */
    private void buildCodes(int n) {
        for (int i = 0; i < n; i++) {
            StringBuilder sb = new StringBuilder();
            int cur = i, par = tree[i].parent;
            while (par != -1) {
                if (tree[par].left == cur) sb.append('0');
                else sb.append('1');
                cur = par;
                par = tree[cur].parent;
            }
            codes[i] = sb.reverse().toString();
            encodeMap.put(tree[i].ch, codes[i]);
            decodeMap.put(codes[i], tree[i].ch);
        }
    }

    /** 編碼:字符串 -> 壓縮二進制字符串 */
    public String encode(String text) {
        StringBuilder sb = new StringBuilder();
        for (char c : text.toCharArray()) {
            String code = encodeMap.get(c);
            if (code == null) throw new IllegalArgumentException("字符不在編碼表中: " + c);
            sb.append(code);
        }
        return sb.toString();
    }

    /** 解碼:壓縮二進制字符串 -> 原文 */
    public String decode(String encoded) {
        StringBuilder sb = new StringBuilder();
        int root = tree.length - 1; // 根節點索引
        int cur = root;
        for (char bit : encoded.toCharArray()) {
            cur = (bit == '0') ? tree[cur].left : tree[cur].right;
            if (tree[cur].isLeaf()) {
                sb.append(tree[cur].ch);
                cur = root; // 重置到根
            }
        }
        return sb.toString();
    }

    /** 獲取編碼表(調試用) */
    public void printCodes() {
        for (int i = 0; i < codes.length; i++) {
            System.out.printf("'%c' (w=%d): %s%n", tree[i].ch, tree[i].weight, codes[i]);
        }
    }

    // ========== 測試主方法 ==========
    public static void main(String[] args) {
        // 示例:字符及頻率
        int[][] data = {
            {'a', 45}, {'b', 13}, {'c', 12}, {'d', 16}, {'e', 9}, {'f', 5}
        };
        HuffmanCoding hc = new HuffmanCoding(data);
        hc.printCodes();
        // 輸出示例:
        // 'f' (w=5): 1100
        // 'e' (w=9): 1101
        // 'c' (w=12): 100
        // 'b' (w=13): 101
        // 'd' (w=16): 00
        // 'a' (w=45): 01

        String text = "abcdef";
        String encoded = hc.encode(text);
        String decoded = hc.decode(encoded);
        System.out.println("原文: " + text);
        System.out.println("編碼: " + encoded);
        System.out.println("解碼: " + decoded);
        System.out.println("驗證: " + text.equals(decoded)); // true
    }
}

關鍵實現細節解析

模塊核心邏輯複雜度
樹構建數組模擬樹,selectMinTwo 線性掃描選最小兩個O(n²) 可優化為 O(n log n) 用優先隊列
編碼生成從葉子向根回溯,左 0 右 1,反轉得編碼O(n * h) h=樹高
編碼壓縮查表拼接字符串O(m) m=文本長度
解碼還原從根按位遍歷樹,遇葉子輸出字符並重置O(k) k=編碼位長

生產環境優化建議

  • 優先隊列優化選最小:用 PriorityQueue 將構建樹降為 O(n log n)。
  • 位操作而非字符串:實際壓縮應將編碼寫入 BitOutputStream,解碼用 BitInputStream,避免字符串開銷。
  • 編碼表序列化:壓縮文件頭需存儲編碼表(或頻率表)以便解碼端重建樹。
  • 處理單字符特殊情況:僅一種字符時編碼為 "0",樹只有根節點。

總結

本文提供了結構清晰、邏輯完整、可直接運行的哈夫曼編碼解碼 Java 實現。通過數組模擬樹、雙向 Map 加速查找、迴溯生成編碼,展示了經典算法的工程化落地要點。理解此實現,可輕鬆擴展至文件壓縮、網絡傳輸優化等實際場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved