【初級算法剖析】計算字符串需要的哈夫曼長度

算法要求和答案並不複雜,不復制,不直接貼結果,而是加一些個人分析,面向非算法專業的人員。

背景:

         哈夫曼編碼,構造一個二叉樹,左支代表編碼0,右支1,從根節點到葉子節點就是編碼,這樣每個樹葉節點都是具有唯一不等長編碼的,通用用於無損壓縮。爲了合理利用空間,頻率趙越高的,越要短,靠近根。每個元素有一個相應的權重,將子節點的權重加和,就表示這一支的總權重。自然權重越小,越要靠底層。所以,構建過程是:取權重最小的2個元素,形成樹,根是權重和。將根放回剩餘元素,迭代這個過程。細節不再解釋。由於解析編碼是從根開始的,這使的一個連續的編碼,有固定的解析路徑,也是就唯一的結果。

要求:

          計算一個字符串需要的編碼長度,編碼權重是單詞各字母的單詞內出現頻率。

技巧:

          初步想法自然是先構建哈夫曼樹,然後計算出每個字母的長度,相加即可。但是,由於頻率並不是全局統計出來,而是相對單詞,所有節點路徑都走且只走一遍,長度和剛好等價於權值和,結果實際是所有葉子節點的路徑,不含根,也可以理解爲含根不含 葉子,葉子和根分別是路徑的兩端,加和等值,計算編碼時, 高度比層次少1,所以要減去一邊。用隊列處理的話,不計根更方便。舉例如下:

字符串:1123,1頻率是2,2、3是1,所以,哈夫曼樹如下,1的編碼是:0,頻率最高也是短,2、3是10,11,總編碼是001011

不含葉節點的話,1高度1,佔2位,可以理解爲加了一次1的權重。2、3各佔1位,高度2 ,可以理解爲加1次。由於根和葉總和等值,所以思路設計爲:

優先隊列取小值,放入所有葉節點,即單詞字母統計,每出2個,加和放回去,直到隊列不足2個,丟棄根節點。這個思路設計,使長度是1時,要單獨返回根節點。

下圖中,圓圈上的都是權重,外面的紅色是數字字符:


代碼如下:

    int GetHFLen(string s) override
    {
        vector<int> c_size(128, 0);     //模型是hash,字母有限,用數組更快速
        for(size_t i = 0; i < s.length(); i++)
        {
            c_size[s[i]-0]++;           //通過-0變char爲整數
        }
        cout << VecToStr(c_size, ' ') << endl;
        priority_queue<int, vector<int>, greater<int>> q;
        for(size_t i = 0; i < c_size.size(); i++)
        {
            int val = c_size[i+'A'];
            if(val > 0)         //因爲用了數組做hash所以要排除空值以降低後續計算量
                q.push(val);
        }
        if(q.size() == 1)       //只有1個節點情況
            return q.top();
        int sum = 0;            //初值0,涵蓋了空串情況
        while(q.size()>1)
        {
            int v1 = q.top();
            q.pop();
            v1 += q.top();
            q.pop();
            sum += v1;
            q.push(v1);
        }
        return sum;
    }



算法:個人認爲,前期靠智商,聰明就行。中後期要靠經驗了,不然智商再好,也是在重走科研路線,耗不起,要基於別人的成果向前。


發佈了87 篇原創文章 · 獲贊 13 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章