定义哈夫曼树节点HuffmanTreeNode
function HuffmanTreeNode (weight, char) {
// 左子树
this.l = null
// 右子树
this.r = null
// 字符的度量值,也就是字符在文本中出现的频次
this.weight = weight || 0
// 字符
this.char = char || ''
}
定义一个最小堆heapMin
主要用于在创建哈夫曼树过程中获取度量值weight(字符出现的频次)最小的节点
/**
* 定义一个最小堆对象
*/
var heapMin = function () {
this.set = [];
}
/**
* 调整堆使其满足最小堆性质
*/
heapMin.prototype.adjust = function (index) {
let len = this.set.length
let l = index * 2 + 1
let r = index * 2 + 2
let min = index
let node = null
if (l <= len -1 && this.set[min].weight > this.set[l].weight) {
min = l
}
if (r <= len -1 && this.set[min].weight > this.set[r].weight) {
min = r
}
if (min != index) {
node = this.set[index];
this.set[index] = this.set[min]
this.set[min] = node
this.adjust(min)
}
}
/**
* 插入一个元素
*/
heapMin.prototype.push = function (node) {
this.set.push(node)
for (let i = Math.floor(this.set.length / 2); i >= 0; i--) {
this.adjust(i)
}
}
/**
* 移除最小元素
*/
heapMin.prototype.pop = function () {
let node
node = this.set.shift()
this.adjust(0)
return node
}
/**
* 获取当前堆大小
*/
heapMin.prototype.size = function () {
return this.set.length
}
/**
* 堆是否为空
*/
heapMin.prototype.empty = function () {
return this.set.length === 0 ? true : false
}
定义哈夫曼编码对象:HuffmanCode
function HuffmanCode () {
// 当前的编码表
this.codeTable = []
// 当前的哈夫曼树
this.huffmanTree = null
}
生成字符频次最小堆
因为javascript中的数组实质上是一个散列数组,因此我们可以将字符直接作为键进行索引
/**
* 统计字符出现的频次,生成字符频次最小堆
*
* str:要进行编码的字符串
* 返回值 返回一个字符串出现频次的最小堆
*/
HuffmanCode.calcHeap = function(str) {
let heap = new heapMin()
let set = []
for (let i = str.length - 1; i >= 0; i--) {
if (set[str[i]]) {
set[str[i]].num++
} else {
set[str[i]] = {num:1,char:str[i]}
}
}
Object.values(set).forEach((value) => {
heap.push(new HuffmanTreeNode(value.num, value.char))
})
return heap
}
创建哈夫曼树
/**
* 创建哈夫曼树
*
* str:要进行哈夫曼编码的字符串
* 返回值:哈夫曼编码树
*/
HuffmanCode.prototype.createHuffmanTree = function(str) {
let heap = HuffmanCode.calcHeap(str)
while(heap.size() > 1) {
let min1 = heap.pop()
let min2 = heap.pop()
let parent = new HuffmanTreeNode(min1.weight + min2.weight, '')
if (min1.weight < min2.weight) {
parent.l = min1
parent.r = min2
} else {
parent.l = min2
parent.r = min1
}
heap.push(parent)
}
this.huffmanTree = heap.pop()
}
递归哈夫曼树,生成编码表
/**
* 递归哈夫曼树,生成编码表
*
* node:当前要递归的结点
* arr:编码表
* code:编码字符串
*
*/
HuffmanCode.traverseTree = function (node, arr, code) {
if (node.l !== null && node.r != null) {
HuffmanCode.traverseTree(node.l, arr, code+'0')
HuffmanCode.traverseTree(node.r, arr, code+'1')
}
arr[node.char] = code
}
哈夫曼编码
/**
* 哈夫曼编码
*/
HuffmanCode.prototype.encode = function (str) {
this.createHuffmanTree(str)
let res = []
HuffmanCode.traverseTree(this.huffmanTree, this.codeTable, '')
for (let i = str.length - 1; i >=0; i--) {
res.push(this.codeTable[str[i]])
}
return res.join('')
}
哈夫曼解码
/**
* 哈夫曼解码
*
* 编码前的字符串
*
*/
HuffmanCode.prototype.decode = function (str) {
if (this.huffmanTree === null) {
console.error('Please create HuffmanTree!');
}
let node = this.huffmanTree
let res = []
for (let len = str.length, i=0; i < len; i++) {
if (str[i] === '0') {
node = node.l
} else {
node = node.r
}
if (node.l === null && node.r === null) {
res.push(node.char)
node = this.huffmanTree
}
}
return res.join('')
}
测试
let huffmanCode = new HuffmanCode()
huffmanCode.encode('我是大白兔欢迎你我是我是力量哈哈')
huffmanCode.decode('10111010')