若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树前端
应用场景文件压缩,又叫压缩算法算法
如今有3课二叉树,都有四个节点,分别带权13,7,8,3数组
一段字符串中计算每个字符重复的次数编码
let a = 'ab cbdal abc' console.log(a.split('').reduce((acc, val) => { acc[val] = (acc[val] || 0) + 1 return acc }, {})) //升级版 const getFreqs = text => [...text] .reduce((acc, val) => (acc[val] ? {...acc, [val]: acc[val] + 1} : {...acc, [val]: 1} ), {}) console.log(getFreqs('abc abx')) //第二种 const cal = str => { let map = {} let i = 0 while (str[i]) { //首先添加str[i]的属性,由于刚开始没值,因此复制为1 map[str[i]] ? map[str[i]]++ : map[str[i]] = 1 i++ } return map } console.log(cal('abc ab cc '))
对于一颗已有的二叉树,若是咱们为他添加一系列新结点,使得他原有的全部结点的度都为2,那么咱们获得了一颗扩充二叉树:设计
其中原有的结点叫作内结点(非叶子结点),新增长的结点叫作叶结点(叶子结点)3d
外结点数=内结点数+1code
总结点数=2*外结点数-1对象
那什么结点的度?blog
- 结点的度指的是二叉树结点的分支数目,若是某个节点没有孩子结点,即便没有分支,那么他的度是0,若是有一个孩子的结点,那么他的度数是1,若是既有左孩子也有右孩子,那么这个结点的度是2
性质区别: 外结点是携带了关键数据的节点,二内部节点没有携带这种数据,只做为导向最终的外结点所走的路径而使用排序
正因如此,咱们的关注点最后是落在赫夫曼树的外结点上,而不是內结点
若是一个数据结点搜索频率越高,就让他分布在离根结点越近的地方,也就是根结点走到该节点通过的路径长度越短
频率是个细化的量,这里咱们使用一个更加标准的一个词描述他---"权值"
**咱们为扩充二叉树的外结点(叶子结点)定义两条属性:权值(W)和路径长度(L),同时规定带权路径长度(WPL) 为扩充二叉树的外结点的权值和路径长度的乘积之和(只是外结点)
外结点的带权路径长度(WPL) = T的根到该节点的路径长度 * 该节点的权值
要设计长短不等的编码,则必须保证:任意一个字符的编码都不是另外一个字符的编码的前缀,这种编码叫作前缀编码
赫夫曼编码就是一种前缀编码,他能解决不等长的译码问题,经过他,咱们能尽量减小编码的长度,同时还能过避免二义性,实现正确编码
赫夫曼树是一棵满二叉树,树中只有两种类型的结点,即叶子节点和度为2的结点,全部树中任意结点的左子树和右子树同时存在
对字符集合按照字符频率进行升序排序,并构建一颗空树
若字符频率不大于根节点频率,则字符做为根节点的左兄弟,造成一个新的根节点,频率值为左、右子节点之和;
若字符频率大于根结点频率,则字符做为根结点的右兄弟,造成一个新的根结点,频率值为左右子节点之和
举例:
1. 作4个叶节点,分别以2,4,5,7为权
2. 从全部入度为0的结点中,最小的两个节点为2和4,则组成6这个分支
3. 从全部入度为0的结点中,最小的两个结点为5和6,则组成11这个分支节点
4. 从全部入度为0的结点中,最小的两个结点为7和11,则组成18这个分支节点,此时只有一个入度为0的顶点为18,组成了下图的二叉树,完成
对字符集合按照频率进行排序,这里使用插入排序
//字符集合为 const contentArr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'] //对应的频率 const valueArr = [5, 3, 4, 0, 2, 1, 8, 6, 9, 7] //使用插入排序 const insertionSort = (valueArr, contentArr) => { let tmpValue, tmpContent for (let i = 1; i < valueArr.length; i++) { tmpValue = valueArr[i] tmpContent = contentArr[i] while (i > 0 && tmpValue < valueArr[i - 1]) { valueArr[i] = valueArr[i - 1] contentArr[i] = contentArr[i - 1] i-- } valueArr[i] = tmpValue contentArr[i] = tmpContent } return contentArr } console.log(insertionSort(valueArr, contentArr)) 我本身写的垃圾排序(经过排序的索引对原数组进行从新排序) let arr = valueArr.slice().sort((a, b) => a - b) arr.reduce((acc, val) => { acc.push(contentArr[valueArr.indexOf(val)]) return acc }, [])
//把字符串重复的转化成重复次数的对象 const getFreqs = text => [...text] .reduce((acc, letter) => (acc[letter] ? { ...acc, [letter]: acc[letter] + 1 } : { ...acc, [letter]: 1 } ), {}); //console.log(getFreqs('abc ab')) //{ a: 2, b: 2, c: 1, ' ': 1 } //把对象转成一个二维数组 const toPairs = freqs => Object.keys(freqs) .map(letter => [letter, freqs[letter]]); //给二维数组排序 const sortPairs = pairs => [...pairs] .sort(([, leftFreq], [, rightFreq]) => leftFreq - rightFreq); //递归建立树 const getTree = pairs => (pairs.length < 2 ? pairs[0] : getTree(sortPairs([ [pairs.slice(0, 2), pairs[0][1] + pairs[1][1]], ...pairs.slice(2)])) ); //递归编码 const getCodes = (tree, pfx = '') => (tree[0] instanceof Array ? Object.assign( getCodes(tree[0][0], `${pfx}0`), getCodes(tree[0][1], `${pfx}1`), ) : { [tree[0]]: pfx }); export default (text) => { const freqs = getFreqs(text); const freqPairs = toPairs(freqs); const sortedFreqPairs = sortPairs(freqPairs); const tree = getTree(sortedFreqPairs); const codes = getCodes(tree); return [...text] .map(letter => codes[letter]) .join(''); };
//字符串转对象 const freqs = text => [...text].reduce((acc, val) => ( (acc[val] ? acc[val] = acc[val] + 1 : acc[val] = 1), acc) , {}) console.log(freqs('abc ab ')) //{ a: 1, b: 1, c: 1, ' ': 1 } //把对象转成二维数组 const topaire = freqs => Object.keys(freqs).map(val => [val, freqs[val]]) console.log(topaire(freqs('abc ab '))) //[ [ 'a', 1 ], [ 'b', 1 ], [ 'c', 1 ], [ ' ', 1 ] ] //二维数组排序 const sortps = pairs => pairs.sort((a, b) => a[1] - b[1]) console.log(sortps(topaire(freqs('abc ab ')))) //[ [ 'c', 1 ], [ 'a', 2 ], [ 'b', 2 ], [ ' ', 2 ] ] //构建树 const tree = ps => { if (ps.length < 2) { return ps[0] } //拿到最小的两个,而后求和,而后把剩下的合并起来,进行递归建立出树 return tree(sortps([[ps.slice(0, 2), ps[0][1] + ps[1][1]]].concat(ps.slice(2)))) } console.log(tree(sortps(topaire(freqs('abc ab '))))) //编码 const codes = (tree, pfx = '') => { if (tree[0] instanceof Array) { return Object.assign(codes(tree[0][0], pfx + '0'), codes(tree[0][1], pfx + '1')) } return ({[tree[0]]: pfx}) } console.log(codes(tree(sortps(topaire(freqs('abc ab ')))))) //将字符串编码 const encode = str => { let output = '编码' let a = {c: '00', a: '01', b: '10', ' ': '11'} for (const item in str) { output = output + a[str[item]] } return output } console.log(encode('abc ab ')) //反码 let c = '011110010010011110101100101101111011111110000000' let dict = { k: '00', c: '0100', d: '0101', a: '011', ' ': '10', b: '110', f: '111' } //第一个参数是编码的参数,第二个参数是字典 const decodeText = (text, dict) => { let encode = '' let decode = '' let keyArr = Object.keys(dict) let valueArr = Object.values(dict) for (let i = 0; i < text.length; i++) { encode += text.charAt(i) for (let j = 0; j < valueArr.length; j++) { if (valueArr[j] == encode) { decode += keyArr[j] text.slice(encode.length) encode = '' } } } return decode } console.log(decodeText(c, dict))
压缩版
function dictionary(text) { const freqs = text => [...text].reduce((fs, c) => (fs[c] ? (fs[c] = fs[c] + 1, fs) : (fs[c] = 1, fs)), {}); const topairs = freqs => Object.keys(freqs).map(c => [c, freqs[c]]); const sortps = pairs => pairs.sort((a, b) => a[1] - b[1]); const tree = ps => (ps.length < 2 ? ps[0] : tree(sortps([[ps.slice(0, 2), ps[0][1] + ps[1][1]]].concat(ps.slice(2))))); const codes = (tree, pfx = '') => (tree[0] instanceof Array ? Object.assign(codes(tree[0][0], pfx + '0'), codes(tree[0][1], pfx + '1')) : { [tree[0]]: pfx }); return codes(tree(sortps(topairs(freqs(text))))); }