若該樹的帶權路徑長度達到最小,稱這樣的二叉樹爲最優二叉樹,也稱爲哈夫曼樹前端
應用場景文件壓縮,又叫壓縮算法算法
如今有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))))); }