每日一題25:Hoffman樹

Hoffman樹是由David A. Hoffman於1952年在MIT攻讀博士學位期間發表的論文《A Method for the Construction of Minimum-Redundancy Codes》中提出的,它的目的尋找一種利用最少量的編碼方法表示信息。Hoffman使用自底向上的方法構建了一棵滿足要求的樹,用這棵樹進行的編碼叫做Hoffman編碼。
用一個節點表示一條信息,每條信息都要知道它們使用的頻率,構建Hoffman樹時,將每個節點看做只有一個節點的一棵樹,這些樹組成了一個森林,算法的流程如下:

  1. 在森林中找到具有最小使用頻率的兩棵樹,爲這兩棵樹生成一個新節點,另新節點成爲這兩棵樹的根,構造起一棵新的樹,新節點的使用頻率爲兩個孩子節點使用頻率之和。
  2. 從森林中刪除剛纔選擇的兩棵樹,加入新生成的那棵樹。
  3. 重複上面兩步,知道森林中只剩下一棵樹。

算法流程很簡單,證明卻很難,我看了原始論文即便都沒弄明白,也許是英文太差了。雖然不能證明,但是實現上面的算法還是比較簡單的,實現中用一個數組保存所有信息,另一個數組保存每條信息的使用頻率,Hoffman樹的節點就由四項組成,信息在數組中的索引,信息使用的頻率,指向節點左孩子、右孩子的指針。這樣設計的理由是信息可能複雜,用一個對象表示,那麼如果出現賦值,性能就不好,更重要的一個理由是Hoffman樹中有兩類節點,一類包含信息(葉子節點),一類不包含信息(非葉子結點),這兩類通過信息來區分是不方便的,特別是非葉子結點的信息是空的,C++中沒有表示空類型的對象,如果節點中保存的是索引,那麼-1就表示該節點是一個非葉子結點,非負索引就可以到數組中取得相應的信息和信息出現的頻率。這樣設計還能帶來另一個好處就是可以讓Hoffman樹更容易的支持插入操作(雖然這個操作不是被使用的可能性比較小)。實現算法的1、2步需要用一個最小堆,我的前一篇博文已經實現了堆數據結構,用到的數組結構也用之前博文實現的Vector結構。

#ifndef _HOFFMAN_H_
#define _HOFFMAN_H_

#include "../include/Vector.h"
#include "../include/Heap.h"
#include "../Utilites/type_traits.h"
#include "../include/Functor.h"

namespace MyDataStructure
{
    template<typename WeightType>
    struct HoffmanNode
    {
        int index;
        WeightType weight;
        HoffmanNode* left;
        HoffmanNode* right;
    };

    //針對HoffmanNode指針特化一個大於比較仿函數,構造
    //最小堆的的時候需要,最小堆中的元素是HoffmanNode
    //型指針
    template<typename WeightType>
    struct greater<HoffmanNode<WeightType>*>
    {
        bool operator ()(HoffmanNode<WeightType>* op1, HoffmanNode<WeightType>* op2)
        {
            return op1->weight > op2->weight;
        }
    };
    template <typename ValueType, typename WeightType>
    class HoffmanTree
    {   
    public:
        typedef typename ParameterTrait<WeightType>::ParameterType WeightParameterType;
        typedef typename HoffmanNode< WeightType>* NodePtr;
        typedef typename HoffmanNode< WeightType> NodeType;
        typedef typename HoffmanTree<ValueType, WeightType> self;
    public:
        HoffmanTree(ValueType values[],WeightType weights[],int count);
        HoffmanTree(const HoffmanTree& rhs);
        self& operator = (const HoffmanTree& rhs);
        ~HoffmanTree();
        void Clear();
        NodePtr GetHead(){ return head; }
    private:
        NodePtr create_node(int index, WeightParameterType weight);
        NodePtr copy_node(const NodePtr node);
        NodePtr merge_node(NodePtr left, NodePtr right);
        void copy(const HoffmanTree& rhs);
        void __copy_tree(NodePtr* dst_node,NodePtr start_node);
        void __clear_tree(NodePtr start_node);
        void create_tree(ValueType values[], WeightType weights[], int count);
        void clear();
    private:
        NodePtr head;
        Vector<ValueType> values;
        Vector<WeightType> weights;
    };

    template<typename ValueType, typename WeightType>
    HoffmanTree<ValueType, WeightType>::HoffmanTree(ValueType values[], WeightType weights[], int count)
    {
        create_tree( values,weights, count);
    }

    template<typename ValueType, typename WeightType>
    HoffmanTree<ValueType, WeightType>::HoffmanTree(const HoffmanTree& rhs)
    {
        clear();
        copy(rhs);
    }

    template<typename ValueType, typename WeightType>
    typename HoffmanTree<ValueType, WeightType>& HoffmanTree<ValueType, WeightType>
        ::operator =(const HoffmanTree& rhs)
    {
        clear();
        copy(rhs);
        return *this;
    }

    template<typename ValueType, typename WeightType>
    HoffmanTree<ValueType, WeightType>::~HoffmanTree()
    {
        clear();
    }

    template<typename ValueType, typename WeightType>
    void HoffmanTree<ValueType, WeightType>::Clear()
    {
        clear();
    }

    template<typename ValueType, typename WeightType>
    typename HoffmanTree<ValueType, WeightType>::NodePtr
        HoffmanTree<ValueType, WeightType>
        ::create_node(int index, WeightParameterType weight)
    {
        NodeType* node = new NodeType;
        node->left = node->right = nullptr;
        node->index = index;
        node->weight = weight;
        return node;
    }

    template<typename ValueType, typename WeightType>
    typename HoffmanTree<ValueType, WeightType>::NodePtr
        HoffmanTree<ValueType, WeightType>
        ::copy_node(const NodePtr node)
    {
        return create_node(node->index, node->weight);
    }

    template<typename ValueType, typename WeightType>
    typename HoffmanTree<ValueType, WeightType>::NodePtr
        HoffmanTree<ValueType, WeightType>
        ::merge_node(NodePtr left, NodePtr right)
    {
        NodeType* node = new NodeType;
        node->left = left;
        node->right = right;
        node->weight = left->weight + right->weight;
        //用index = -1表示這是一個內部節點
        node->index = -1;
        return node;
    }

    template<typename ValueType, typename WeightType>
    void HoffmanTree<ValueType, WeightType>
        ::copy(const HoffmanTree& rhs)
    {
        values = rhs.values;
        weights = rhs.weights;
        __copy_tree(&head, rhs.head);
    }

    template<typename ValueType, typename WeightType>
    void HoffmanTree<ValueType, WeightType>
        ::__copy_tree(NodePtr* dst_node, NodePtr start_node)
    {
        if (start_node == nullptr) *dst_node = nullptr;
        else
        {
            NodeType* node = copy_node(start_node);
            *dst_node = node;
            __copy_tree(&((*dst_node)->left), start_node->left);
            __copy_tree(&((*dst_node)->right), start_node->right);
        }
    }

    template<typename ValueType, typename WeightType>
    void HoffmanTree<ValueType, WeightType>::clear()
    {
        __clear_tree(head);
        values.Clear();
    }

    template<typename ValueType, typename WeightType>
    void HoffmanTree<ValueType, WeightType>
        ::__clear_tree(NodePtr start_node)
    {
        if (start_node == nullptr) return;
        else
        {
            if (start_node->left != nullptr) __clear_tree(start_node->left);
            if (start_node->right != nullptr) __clear_tree(start_node->right);
            delete start_node;
            start_node = nullptr;
        }
    }

    template<typename ValueType, typename WeightType>
    void HoffmanTree<ValueType, WeightType>
        ::create_tree(ValueType values[], WeightType weights[], int count)
    {
        Heap<NodePtr, greater<NodePtr>> minHeap;
        for (int i = 0; i < count; ++i) 
        {
            NodeType* node = create_node(i, weights[i]);
            this->values.PushBack(values[i]);
            this->weights.PushBack(weights[i]);
            minHeap.Insert(node);
        }
        while (minHeap.Size() > 1)
        {
            NodePtr node1,node2;
            //取使用頻率最小的樹
            minHeap.GetTop(node1);
            //從堆中移除使用頻率最小的樹
            minHeap.RemoveTop();
            //取使用頻率第二小的樹
            minHeap.GetTop(node2);
            //從堆中移除使用頻率第二小的樹
            minHeap.RemoveTop();
            //構造新樹
            NodePtr new_node = merge_node(node1, node2);
            //將新樹插入最小堆中
            minHeap.Insert(new_node);
        }
        minHeap.GetTop(head);
    }
}

#endif

測試代碼:

// HoffmanTest.cpp : 定義控制檯應用程序的入口點。
//

#include "stdafx.h"
#include "../include/Hoffman.h"
#include <iostream>

using namespace MyDataStructure;
using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
    char values[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i','j' };
    float weights[] = { 0.24f, 0.84f, 0.03f, 0.51f, 0.12f, 0.79f, 0.93f, 0.34f, 0.61f, 0.46f };
    HoffmanTree<char, float> H(values,weights,10);
    HoffmanTree<char, float> H1(H);
    HoffmanTree<char, float> H2 = H1;
    float sum = 0.0f;
    for (int i = 0; i < 10; ++i)
    {
        sum += weights[i];
    }
    cout << sum << endl;
    return 0;
}

看看程序運行結果:
這裏寫圖片描述
這是我用調試狀態下查看構造出來的Hoffman樹畫出來的,本來寫了一個打印二叉樹的程序,但是由於想做到跨平臺、支持多種設備遲遲沒有做出來,主要是卡在打印字符的這一步,如果哪位小夥伴有好的點子或資料還請推薦給我。雖然程序沒寫完,但是卻收穫了不少東西,最重要的收穫記錄在了前一篇博客裏面。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章