哈夫曼樹的js實現

前言

哈夫曼樹是數據壓縮編碼算法的基礎,本文使用JavaScript語言實現了該算法。
算法流程:輸入待編碼的字符串,算法去構造哈夫曼樹,從而實現對字符串的二進制壓縮編碼。

對於哈夫曼樹理論的學習,可去參見其他文章。本文僅包含實現的代碼以及註釋。
註釋比較豐富,相信不難理解。

算法實現

樹節點

既然是樹數據結構,就要有樹節點,下面是樹節點定義

class Node {  
    constructor(value, char, left, right) {  
        this.val = value; // 字符出現次數  
        this.char = char; // 待編碼字符  
        this.left = left;  
        this.right = right;  
    }  
}

一般來說,節點只需要val,left,right即可,這裏加了一個char字段,表示該節點代表待編碼字符串裏面的哪個字符,當前節點是葉子節點的時候,會賦值這個字段。

核心代碼

構造函數
class huffmanTree{ 
    constructor(str){  
        // 第一步,統計字符出現頻率  
        let hash = {};  
        for(let i = 0; i < str.length; i++){  
            hash[str[i]] = ~~hash[str[i]] + 1;  
        }  
        this.hash = hash;  
  
        // 第二步,構造哈夫曼樹  
        this.huffmanTree = this.getHuffmanTree();  
  
        // 第三步,遍歷哈夫曼樹,得到編碼表
        let map = this.getHuffmanCode(this.huffmanTree);  
        // 查看編碼表,即每個字符的二進制編碼是什麼  
        console.log(map);  
  
        // 第四部,根據編碼對照表,返回最終的二進制編碼  
        this.binaryStr = this.getBinaryStr(map, str);  
    } 
}

下面我們逐一的看一下,(1)構造哈夫曼樹的過程、(2)遍歷哈弗曼樹取得編碼表的過程 以及 (3)返回最終二進制串的過程。

構造哈夫曼樹
    // 構造哈夫曼樹  
    getHuffmanTree(){  
        // 以各個字符出現次數爲node.val, 構造森林  
        let forest = []  
        for(let char in this.hash){  
            let node = new Node(this.hash[char], char); 
            forest.push(node);  
        }  
  
        let allNodes = []; // 存放被合併的節點,因爲不能真的刪除森林中任何一個節點,否則.left .right就找不到節點了  
        // 等到森林只剩一個節點時,表示合併過程結束,樹就生成了
        while(forest.length !== 1){  
            // 從森林中找到兩個最小的樹,合併之  
            forest.sort((a, b) => {  
                return a.val - b.val;  
            });  
  
            let node = new Node(forest[0].val + forest[1].val, '');  
            allNodes.push(forest[0]);  
            allNodes.push(forest[1]);  
            node.left = allNodes[allNodes.length - 2]; // 左子樹放置詞頻低的  
            node.right = allNodes[allNodes.length - 1]; // 右子樹放置詞頻高的  
  
            // 刪除最小的兩棵樹  
            forest = forest.slice(2);  
            // 新增的樹加入  
            forest.push(node);  
        }  
  
        // 生成的哈夫曼樹,僅剩一個節點,即整棵樹的根節點
        return forest[0];  
    } 
遍歷哈夫曼樹,返回編碼表
    // 遍歷哈夫曼樹,返回一個 原始字符 和 二進制編碼 的對照表  
    getHuffmanCode(tree){  
        let hash = {};  // 對照表
        let traversal = (node, curPath) => {  
            if (!node.length && !node.right) return;  
            if (node.left && !node.left.left && !node.left.right){  
                hash[node.left.char] = curPath + '0';  
            }  
            if (node.right && !node.right.left && !node.right.right){  
                hash[node.right.char] = curPath + '1';  
            }  
            // 往左遍歷,路徑加0  
            if(node.left){  
                traversal(node.left, curPath + '0');  
            }  
            // 往右遍歷,路徑加1  
            if(node.right){  
                traversal(node.right, curPath + '1');  
            }  
        };  
        traversal(tree, '');  
        return hash;  
    }  
返回編碼串
    // 返回最終的壓縮後的二進制串  
    getBinaryStr(map, originStr){  
        let result = '';  
        for(let i = 0; i < originStr.length; i++){  
            result += map[originStr[i]];  
        }  
        return result;  
    }  
代碼彙總
// 哈弗曼編碼是將一個 字符串序列 用 二進制表示 的壓縮算法  
class huffmanTree{  
    constructor(str){  
        // 第一步,統計字符出現頻率  
        let hash = {};  
        for(let i = 0; i < str.length; i++){  
            hash[str[i]] = ~~hash[str[i]] + 1;  
        }  
        this.hash = hash;  
  
        // 構造哈夫曼樹  
        this.huffmanTree = this.getHuffmanTree();  
  
        let map = this.getHuffmanCode(this.huffmanTree);  
        // 查看對照表,即每個字符的二進制編碼是什麼  
        console.log(map);  
  
        // 最終的二進制編碼  
        this.binaryStr = this.getBinaryStr(map, str);  
    }  
  
    // 構造哈夫曼樹  
    getHuffmanTree(){  
        // 以各個字符出現次數爲node.val, 構造森林  
        let forest = []  
        for(let char in this.hash){  
            let node = new Node(this.hash[char], char); 
            forest.push(node);  
        }  
  
        // 等到森林只剩一個節點時,表示合併過程結束,樹就生成了  
        let allNodes = []; // 存放被合併的節點,因爲不能真的刪除森林中任何一個節點,否則.left .right就找不到節點了  
        while(forest.length !== 1){  
            // 從森林中找到兩個最小的樹,合併之  
            forest.sort((a, b) => {  
                return a.val - b.val;  
            });  
  
            let node = new Node(forest[0].val + forest[1].val, '');  
            allNodes.push(forest[0]);  
            allNodes.push(forest[1]);  
            node.left = allNodes[allNodes.length - 2]; // 左子樹放置詞頻低的  
            node.right = allNodes[allNodes.length - 1]; // 右子樹放置詞頻高的  
  
            // 刪除最小的兩棵樹  
            forest = forest.slice(2);  
            // 新增的樹加入  
            forest.push(node);  
        }  
  
        // 生成的哈夫曼樹  
        return forest[0];  
    }  
  
    // 遍歷哈夫曼樹,返回一個 原始字符 和 二進制編碼 的對照表  
    getHuffmanCode(tree){  
        let hash = {};  // 對照表
        let traversal = (node, curPath) => {  
            if (!node.length && !node.right) return;  
            if (node.left && !node.left.left && !node.left.right){  
                hash[node.left.char] = curPath + '0';  
            }  
            if (node.right && !node.right.left && !node.right.right){  
                hash[node.right.char] = curPath + '1';  
            }  
            // 往左遍歷,路徑加0  
            if(node.left){  
                traversal(node.left, curPath + '0');  
            }  
            // 往右遍歷,路徑加1  
            if(node.right){  
                traversal(node.right, curPath + '1');  
            }  
        };  
        traversal(tree, '');  
        return hash;  
    }  
  
    // 返回最終的壓縮後的二進制串  
    getBinaryStr(map, originStr){  
        let result = '';  
        for(let i = 0; i < originStr.length; i++){  
            result += map[originStr[i]];  
        }  
        return result;  
    }  
}

測試代碼

let tree = new huffmanTree('ABBCCCDDDDEEEEE')  
console.log(tree)

對照表:{ C: ‘00’, A: ‘010’, B: ‘011’, D: ‘10’, E: ‘11’ }
最終編碼結果:010011011000000101010101111111111

結語

前端算法庫:https://github.com/cunzaizhuyi/js-leetcode
這裏記錄了我刷過的近500道LeetCode的題解,
希望對前端同行找工作面試、修煉算法內功有幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章