(算法)壓縮算法(哈夫曼樹)

哈夫曼樹(赫夫曼樹/霍夫曼樹 /最優樹)

若該樹的帶權路徑長度達到最小,稱這樣的二叉樹爲最優二叉樹,也稱爲哈夫曼樹前端

應用場景文件壓縮,又叫壓縮算法算法

如今有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

赫夫曼樹的外結點和內結點

性質區別: 外結點是攜帶了關鍵數據的節點,二內部節點沒有攜帶這種數據,只做爲導向最終的外結點所走的路徑而使用排序

正因如此,咱們的關注點最後是落在赫夫曼樹的外結點上,而不是內結點

帶權路徑長度WPL

若是一個數據結點搜索頻率越高,就讓他分佈在離根結點越近的地方,也就是根結點走到該節點通過的路徑長度越短

頻率是個細化的量,這裏咱們使用一個更加標準的一個詞描述他---"權值"

**咱們爲擴充二叉樹的外結點(葉子結點)定義兩條屬性:權值(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)))));
}

待續...

相關文章
相關標籤/搜索