哈夫曼樹(一)之 C語言詳解

本章介紹哈夫曼樹。和以往同樣,本文會先對哈夫曼樹的理論知識進行簡單介紹,而後給出C語言的實現。後續再分別給出C++和Java版本的實現;實現的語言雖不一樣,可是原理一模一樣,選擇其中之一進行了解便可。若文章有錯誤或不足的地方,請幫忙指出! html

目錄 node

1. 哈夫曼樹的介紹
2. 哈夫曼樹的圖文解析
3. 哈夫曼樹的基本操做
4. 哈夫曼樹的完整源碼
git


轉載請註明出處:http://www.cnblogs.com/skywang12345/github

更多內容:數據結構與算法系列 目錄算法

哈夫曼樹的介紹

Huffman Tree,中文名是哈夫曼樹或霍夫曼樹,它是最優二叉樹。數組

定義:給定n個權值做爲n個葉子結點,構造一棵二叉樹,若樹的帶權路徑長度達到最小,則這棵樹被稱爲哈夫曼樹。 這個定義裏面涉及到了幾個陌生的概念,下面就是一顆哈夫曼樹,咱們來看圖解答。數據結構

(01) 路徑和路徑長度測試

定義:在一棵樹中,從一個結點往下能夠達到的孩子或孫子結點之間的通路,稱爲路徑。通路中分支的數目稱爲路徑長度。若規定根結點的層數爲1,則從根結點到第L層結點的路徑長度爲L-1。
例子:100和80的路徑長度是1,50和30的路徑長度是2,20和10的路徑長度是3。
ui

(02) 結點的權及帶權路徑長度spa

定義:若將樹中結點賦給一個有着某種含義的數值,則這個數值稱爲該結點的權。結點的帶權路徑長度爲:從根結點到該結點之間的路徑長度與該結點的權的乘積。
例子:節點20的路徑長度是3,它的帶權路徑長度= 路徑長度 * 權 = 3 * 20 = 60。

(03) 樹的帶權路徑長度

定義:樹的帶權路徑長度規定爲全部葉子結點的帶權路徑長度之和,記爲WPL。
例子:示例中,樹的WPL= 1*100 + 2*80 + 3*20 + 3*10 = 100 + 160 + 60 + 30 = 350。


比較下面兩棵樹

上面的兩棵樹都是以{10, 20, 50, 100}爲葉子節點的樹。

左邊的樹WPL=2*10 + 2*20 + 2*50 + 2*100 = 360
右邊的樹WPL=350

左邊的樹WPL > 右邊的樹的WPL。你也能夠計算除上面兩種示例以外的狀況,但實際上右邊的樹就是{10,20,50,100}對應的哈夫曼樹。至此,應該堆哈夫曼樹的概念有了必定的瞭解了,下面看看如何去構造一棵哈夫曼樹。

哈夫曼樹的圖文解析

假設有n個權值,則構造出的哈夫曼樹有n個葉子結點。 n個權值分別設爲 w1、w2、…、wn,哈夫曼樹的構造規則爲:

1. 將w1、w2、…,wn當作是有n 棵樹的森林(每棵樹僅有一個結點);
2. 在森林中選出根結點的權值最小的兩棵樹進行合併,做爲一棵新樹的左、右子樹,且新樹的根結點權值爲其左、右子樹根結點權值之和;
3. 從森林中刪除選取的兩棵樹,並將新樹加入森林;
4. 重複(02)、(03)步,直到森林中只剩一棵樹爲止,該樹即爲所求得的哈夫曼樹。


以{5,6,7,8,15}爲例,來構造一棵哈夫曼樹。

第1步:建立森林,森林包括5棵樹,這5棵樹的權值分別是5,6,7,8,15。
第2步:在森林中,選擇根節點權值最小的兩棵樹(5和6)來進行合併,將它們做爲一顆新樹的左右孩子(誰左誰右可有可無,這裏,咱們選擇較小的做爲左孩子),而且新樹的權值是左右孩子的權值之和。即,新樹的權值是11。 而後,將"樹5"和"樹6"從森林中刪除,並將新的樹(樹11)添加到森林中。
第3步:在森林中,選擇根節點權值最小的兩棵樹(7和8)來進行合併。獲得的新樹的權值是15。 而後,將"樹7"和"樹8"從森林中刪除,並將新的樹(樹15)添加到森林中。
第4步:在森林中,選擇根節點權值最小的兩棵樹(11和15)來進行合併。獲得的新樹的權值是26。 而後,將"樹11"和"樹15"從森林中刪除,並將新的樹(樹26)添加到森林中。
第5步:在森林中,選擇根節點權值最小的兩棵樹(15和26)來進行合併。獲得的新樹的權值是41。 而後,將"樹15"和"樹26"從森林中刪除,並將新的樹(樹41)添加到森林中。
此時,森林中只有一棵樹(樹41)。這棵樹就是咱們須要的哈夫曼樹!

哈夫曼樹的基本操做

哈夫曼樹的重點是如何構造哈夫曼樹。本文構造哈夫曼時,用到了之前介紹過的"(二叉堆)最小堆"。下面對哈夫曼樹進行講解。

1. 基本定義

typedef int Type;

typedef struct _HuffmanNode {
    Type key;                     // 權值
    struct _HuffmanNode *left;    // 左孩子
    struct _HuffmanNode *right;   // 右孩子
    struct _HuffmanNode *parent;  // 父節點
} HuffmanNode, *HuffmanTree;

HuffmanNode是哈夫曼樹的節點類。

2. 構造哈夫曼樹

/*
 * 建立Huffman樹
 *
 * 參數說明:
 *     a 權值數組
 *     size 數組大小
 *
 * 返回值:
 *     Huffman樹的根
 */
HuffmanNode* create_huffman(Type a[], int size)
{
    int i;
    HuffmanNode *left, *right, *parent;

    // 創建數組a對應的最小堆
    create_minheap(a, size);

    for(i=0; i<size-1; i++)
    {   
        left = dump_from_minheap();  // 最小節點是左孩子
        right = dump_from_minheap(); // 其次纔是右孩子

        // 新建parent節點,左右孩子分別是left/right;
        // parent的大小是左右孩子之和
        parent = huffman_create_node(left->key+right->key, left, right, NULL);
        left->parent = parent;
        right->parent = parent;


        // 將parent節點數據拷貝到"最小堆"中
        if (dump_to_minheap(parent)!=0)
        {
            printf("插入失敗!\n結束程序\n");
            destroy_huffman(parent);
            parent = NULL;
            break;
        }
    }   

    // 銷燬最小堆
    destroy_minheap();

    return parent;
}

首先經過create_huffman(a, size)來一個最小堆。最小堆構造完成以後,進入for循環。

每次循環時:

(01) 首先,將最小堆中的最小節點拷貝一份並賦值給left,而後重塑最小堆(將最小節點和後面的節點交換位置,接着將"交換位置後的最小節點"以前的所有元素從新構形成最小堆);
(02) 接着,再將最小堆中的最小節點拷貝一份並將其賦值right,而後再次重塑最小堆;
(03) 而後,新建節點parent,並將它做爲left和right的父節點;
(04) 接着,將parent的數據複製給最小堆中的指定節點。

二叉堆中已經介紹過堆,這裏就再也不對堆的代碼進行說明了。如有疑問,直接參考後文的源碼。其它的相關代碼,也Please RTFSC(Read The Fucking Source Code)!

哈夫曼樹的完整源碼

哈夫曼樹的源碼共包括4個文件。

1. 哈夫曼樹的頭文件(huffman.h)

2. 哈夫曼樹的實現文件(huffman.c)

3. 哈夫曼樹對應的最小堆(minheap.c)

4. 哈夫曼樹的測試程序(huffman_test.c)

相關文章
相關標籤/搜索