C++算法之哈夫曼樹

jopen 9年前發布 | 5K 次閱讀 C/C++ 算法

在數據傳輸的過程當中,我們總是希望用盡可能少的帶寬傳輸更多的數據,哈夫曼就是其中的一種較少帶寬傳輸的方法。哈夫曼的基本思想不復雜,那就是對于出現頻率高的數據用短字節表示,對于頻率比較低得數據用長字節表示。   比如說,現在有4個數據需要傳輸,分別為A、B、C、D,所以一般來說,如果此時沒有考慮四個數據出現的概率,那么我們完全可以這么分配,平均長度為2,

/*

  • A - 00 B - 01
  • C - 10 D - 11 */

</pre>     但是,現在條件發生了改變,四個數據出現的頻率并不一樣,分別為0.1/0.2/0.3/0.4。那么這時候應該怎么分配長度呢,其實也簡單。我們只要把所有數據按照頻率從低到高排列,每次取前兩位合并成新的節點,再把這個新節點放到隊列中重新排序即可。新節點的左結點默認設為1,右結點默認設為0。然后重復上面的過程,直到所有的節點都合并成一個節點為止。如果應用到實際的示例中,合并的過程應該是這樣的,
    第一步,首先合并A和B,因為A和B是概率最小的

/*

  • total_1(0.3) C (0.3) D(0.4)
  • / \
  • A(0.1) B(0.2) /
    </pre>     第二步,接著合并total_1和C,
    / 
  • total_2 (0.6)
  • / \
  • total_1(0.3) C (0.3) D(0.4)
  • / \
  • A(0.1) B(0.2) /
    </pre>     最后一步,合并total_2和D,
    / 
  • final (1.0)
  • / \
  • D (0.4) total_2 (0.6)
  • / \
  • total_1(0.3) C (0.3)
  • / \
  • A(0.1) B(0.2) */

//該代碼片段來自于: http://www.sharejs.com/codes/cpp/6552</pre>     所以按照上面的生成樹,數據的編號應該這么安排,

/*

  • A - 011 B - 010
  • C - 00 D - 1 /
    </pre>     看上去A和B的長度還增加了,但是D的長度減少了。那么整個數據的平均長度有沒有減少呢?我們可以計算一下。3 
     0.1 + 3  0.2 + 2  0.3 + 0.4 = 1.9 < 2。我們發現調整后的數據平均長度比原來減少了近(2 - 1.9)/2  100% = 10 %,這可是巨大的發現啊。
        為了完成整個哈夫曼樹的創建,我們還需要定義一個數據結構:
    typedef struct _HUFFMAN_NODE
    {
    char str;
    double frequence;
    int symbol;
    struct _HUFFMAN_NODE
    left;
    struct _HUFFMAN_NODE right;
    struct _HUFFMAN_NODE
    parent;

}HUFFMAN_NODE;

//該代碼片段來自于: http://www.sharejs.com/codes/cpp/6552</pre>     其中str記錄字符,frequency記錄字符出現的頻率, symbol記錄分配的數據,左子樹為1、右子樹為0,left為左子樹,right為右子樹,parent為父節點。接下來,我們從創建huffman結點開始。

HUFFMAN_NODE create_new_node(char str, double frq)
{
HUFFMAN_NODE
pNode = (HUFFMAN_NODE*)malloc(sizeof(HUFFMAN_NODE));
assert(NULL != pNode);

pNode->str = str;  
pNode->frequence = frq;  
pNode->symbol = -1;  
pNode->left = NULL;  
pNode->right = NULL;  
pNode->parent = NULL;  
return pNode;  

}

</pre>    前面說到了哈夫曼樹的創建,那下面一個重要的環節就是哈夫曼樹的排序問題。但是由于排序的內容是數據結構,因此形式上說,我們需要采用通用數據排序算法,這在我之前的博客里面已經涉及到了(通用算法設計)。所以,我們所要做的就是編寫compare和swap兩個函數。通用冒泡代碼如下所示,

void bubble_sort(void array[], int length, int (compare)(void, void), void(*swap)(void, void))
{
int outer;
int inner;

for(outer = length -1; outer >0; outer --){  
    for(inner = 0; inner < outer; inner ++){  
        if(compare(array[inner], array[inner + 1]))  
            swap(&array[inner], &array[inner + 1]);  
    }  
}  

return;  

}
</pre>     compare和swap代碼如下所示,

int compare (void a, void b)
{
HUFFMAN_NODE node1 = (HUFFMAN_NODE)a;
HUFFMAN_NODE node2 = (HUFFMAN_NODE)b;

return node1->frequence > node2->frequence ? 1 : 0;  

}

void swap(void a, void b)
{
HUFFMAN_NODE* median;
HUFFMAN_NODE node1 = (HUFFMAN_NODE)a;
HUFFMAN_NODE node2 = (HUFFMAN_NODE)b;

median = *node1;  
*node1 = *node2;  
*node2 = median;  

}
</pre>     有了創建函數和排序函數,那么哈夫曼樹就可以創建了,

HUFFMAN_NODE create_huffman_tree(HUFFMAN_NODE huffmanNode[], int length)
{
HUFFMAN_NODE* head = NULL;

if(NULL == huffmanNode ||  length <= 1)  
    return NULL;  

while(length > 1){  
    bubble_sort((void**)huffmanNode, length, compare, swap);  
    head = create_new_node('\0',  huffmanNode[0]->frequence + huffmanNode[1]->frequence);  
    assert(NULL != head);  

    head->left = huffmanNode[0];  
    head->right = huffmanNode[1];  
    huffmanNode[0]->parent = head;  
    huffmanNode[0]->symbol = 1;  
    huffmanNode[1]->parent = head;  
    huffmanNode[1]->symbol = 0;  

    memmove(&huffmanNode[0], &huffmanNode[2], sizeof(HUFFMAN_NODE*) * (length -2));  
    huffmanNode[length -2] = head;  
    length --;  
}  

return head;  

}
</pre>     上面的代碼完整了寫出了huffman樹的創建過程,那么我們怎么知道符號的編碼是多少呢?這其實不難,因為根節點都知道了,我們只要按照自下而上的順序遍歷節點就可以打印出編碼,只不過編碼是逆序的而已,

void print_code_for_str(HUFFMAN_NODE pNode, HUFFMAN_NODE head)
{
if(NULL == pNode || NULL == head)
return;

while(head != pNode){  
    printf("%d", pNode->symbol);  
    pNode = pNode->parent;  
}  

return;  

}
</pre>     如果對代碼本身還有懷疑,可以編譯一個測試用例驗證一下,

void test()
{
HUFFMAN_NODE node1 = NULL;
HUFFMAN_NODE
node2 = NULL;
HUFFMAN_NODE node3 = NULL;
HUFFMAN_NODE
node4 = NULL;

HUFFMAN_NODE* test[] = {node1 = create_new_node('a', 0.1),  
    node2 = create_new_node('b', 0.2),  
    node3 = create_new_node('c', 0.3),  
    node4 = create_new_node('d', 0.4),  
};  

HUFFMAN_NODE* head = create_huffman_tree(test, sizeof(test)/sizeof(HUFFMAN_NODE*));  
print_code_for_str(node1, head);  
print_code_for_str(node2, head);  
print_code_for_str(node3, head);  
print_code_for_str(node4, head);  

}
</pre>
總結:
    (1)哈夫曼樹不復雜,如果手算可以成功,那么編程應該也沒有什么問題
    (2)復雜算法都是由小算法搭積木而成的,朋友們應該在基本算法上打下堅實的基礎
    (3)算法注意復用,這里就用到了原來講到的通用算法內容

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!