1. 程式人生 > >Java資料結構和演算法:哈夫曼樹

Java資料結構和演算法:哈夫曼樹

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

哈夫曼樹的介紹

在一棵二叉樹中由根結點到某個結點所經過的分支序列叫做由根結點到這個結點的路徑,由根結點到某個結點所經過的分支數稱為由根結點到該結點的路徑長度。由根結點到所有葉結點的路徑長度之和稱為該二叉樹的路徑長度。

如果二叉樹中每一個葉結點都帶有某一確定值,就可以將二叉樹的路徑長度的概念加以推廣。設一棵具有n個帶權值葉結點的二叉樹,那麼從根結點到各個葉結點的路徑長度與對應葉結點權值的乘積之和叫做二叉樹的帶權路徑長度,記作:

其中Wk為第k個葉結點的權值,Lk為第K個葉結點的路徑長度。

如果給定一組具體確定權值的葉結點,可以構造出葉結點數相同的形態各異的二叉樹。例如,給定4個葉子結點,其權值分別為(3、5、9、12)。可以構造出多棵形狀不同的二叉樹,它們的帶權路徑長度不一定相同。圖6-21給出了其中三棵不同形狀的二叉樹。它們的帶權路徑長度為分別為58、54和76。

哈夫曼樹

由此可見,對於一組確定權值的葉結點,所構造出的不同形態二叉樹的帶權路徑長度並不相同。在此把其中具有最小帶權路徑長度的二叉樹稱為最優二叉樹,最優二叉樹也稱作哈夫曼樹,可以證明,圖(c)所示的二叉樹就是一棵哈夫曼樹。

根據哈夫曼樹的定義,要使一棵二叉樹的其WPL值最小,顯然必須使權值越大的葉結點越靠近根結點,而權值越小的結點越遠離根結點。

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

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

哈夫曼樹

路徑和路徑長度

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

結點的權及帶權路徑長度

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

樹的帶權路徑長度

定義:樹的帶權路徑長度規定為所有葉子結點的帶權路徑長度之和,記為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)!

哈夫曼樹在編碼問題中的應用

在資料通訊中,經常需要將傳送的電文轉換成由二進位制數字0、1組成的串,一般稱之為編碼。例如,假設要傳送的電文為AADDBCAAABDDCADAAADD,電文中只有A、B、C、D四種字元;若這四種字元的編碼分別為:A(00)、B(01)、C(10)、D(11),則電文的程式碼為:0000111101100000000111111000110000001111,電文程式碼的長度為40。在這種編碼方案中,四種字元的編碼長度均為2,這是一種等長編碼。

在傳送電文時,人們總是希望傳送時間儘可能短,這就要求使電文程式碼長度儘可能短。顯然,上述編碼方案所產生的電文程式碼還不夠短。如果這四種字元的編碼分別為:A(0)、B(1)、C(10)、D(01),則用此編碼方案對上述電文進行編碼得到的電文程式碼為:00010111000010101100010000101,此電文程式碼的長度只有29。但這樣的電文程式碼是無法正確翻譯成原來的電文的。顯然對“01”,你可以認為是“D”,也可以認為是“AB”。因為這四個字元的編碼不是字首碼(字首碼要求任一字元的編碼均非其他字元編碼的字首),因而無法獲得唯一的譯碼。

可以利用哈夫曼樹構造出使電文程式碼總長最短的編碼方案。具體做法如下:設需要編碼的字元集合為{C1、C2、…、Cn},它們在電文中出現的次數或頻率集合為{W1、W2、…、Wn}。以C1、C2、…、Cn作為葉結點,W1、W2、…、Wn作為它們的權值,構造一棵哈夫曼樹,規定哈夫曼樹中的左分支代表0,右分支代表1,則從根結點到每個葉結點所經過的路徑分支組成的0或1序列作為該葉結點對應字元的編碼,我們稱之為哈夫曼編碼。

例如:對於一段電文:AADDBCAAABDDCADAAADDCDDBAACCA。其字元集合為:{A、B、C、D},各個字元出現的頻率(次數)是 W={12、3、5、9}。若給每個字元以等長編碼:A(00)、B(10)、C(01)、D(11)。則電文程式碼的長度為:(12+3+5+9)*2=58。

若按各個字元出現的頻率不同而給予不等長編碼,可望減少電文程式碼的長度。因各字元出現的頻率為{12/29、3/29、5/29、9/29},化成整數為{12、3、5、9},以它們為各葉結點上的權值,建立哈夫曼樹,如圖6-23所示,得哈夫曼編碼為:A(0)、B(100)、C(101)、D(11)。它的總程式碼長度:12*1+(3+5)*2+9*3=54,正好等於哈夫曼樹的帶權路徑長度WPL,顯然比等長編碼的程式碼長度要短。哈夫曼編碼是一種字首編碼,解碼時不會混淆。

定長編碼、不定長編碼,字首編碼、非字首編碼,左0右1,帶權編碼

哈夫曼樹

哈夫曼樹

1. 哈夫曼樹的介紹

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

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

1.1 路徑和路徑長度

定義:在一棵樹中,從一個結點往下可以達到的孩子或孫子結點之間的通路,稱為路徑。通路中分支的數目稱為路徑長度。若規定根結點的層數為1,則從根結點到第L層結點的路徑長度為L-1。

例子:100和80的路徑長度是1,50和30的路徑長度是2,20和10的路徑長度是3。

1.2 結點的權及帶權路徑長度

定義:若將樹中結點賦給一個有著某種含義的數值,則這個數值稱為該結點的權。結點的帶權路徑長度為:從根結點到該結點之間的路徑長度與該結點的權的乘積。

例子:節點20的路徑長度是3,它的帶權路徑長度= 路徑長度 * 權 = 3 * 20 = 60。

1.3 樹的帶權路徑長度

定義:樹的帶權路徑長度規定為所有葉子結點的帶權路徑長度之和,記為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}對應的哈夫曼樹。至此,應該堆哈夫曼樹的概念有了一定的瞭解了,下面看看如何去構造一棵哈夫曼樹。

2. 哈夫曼樹的圖文解析

假設有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)。這棵樹就是我們需要的哈夫曼樹!

3. 哈夫曼樹的基本操作

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

基本定義

public class HuffmanNode implements Comparable, Cloneable {
    protected int key;              // 權值
    protected HuffmanNode left;     // 左孩子
    protected HuffmanNode right;    // 右孩子
    protected HuffmanNode parent;   // 父結點

    protected HuffmanNode(int key, HuffmanNode left, HuffmanNode right, HuffmanNode parent) {
        this.key = key;
        this.left = left;
        this.right = right;
        this.parent = parent;
    }

    @Override
    public Object clone() {
        Object obj=null;

        try {
            obj = (HuffmanNode)super.clone();//Object 中的clone()識別出你要複製的是哪一個物件。    
        } catch(CloneNotSupportedException e) {
            System.out.println(e.toString());
        }

        return obj;    
    }

    @Override
    public int compareTo(Object obj) {
        return this.key - ((HuffmanNode)obj).key;
    }
}

HuffmanNode是哈夫曼樹的節點類。

public class Huffman {

    private HuffmanNode mRoot;  // 根結點

    ...
}

Huffman是哈夫曼樹對應的類,它包含了哈夫曼樹的根節點和哈夫曼樹的相關操作。

4. 構造哈夫曼樹

/* 
 * 建立Huffman樹
 *
 * @param 權值陣列
 */
public Huffman(int a[]) {
    HuffmanNode parent = null;
    MinHeap heap;

    // 建立陣列a對應的最小堆
    heap = new MinHeap(a);

    for(int i=0; i<a.length-1; i++) {   
        HuffmanNode left = heap.dumpFromMinimum();  // 最小節點是左孩子
        HuffmanNode right = heap.dumpFromMinimum(); // 其次才是右孩子

        // 新建parent節點,左右孩子分別是left/right;
        // parent的大小是左右孩子之和
        parent = new HuffmanNode(left.key+right.key, left, right, null);
        left.parent = parent;
        right.parent = parent;

        // 將parent節點資料拷貝到"最小堆"中
        heap.insert(parent);
    }

    mRoot = parent;

    // 銷燬最小堆
    heap.destroy();
}

首先建立最小堆,然後進入for迴圈。每次迴圈時:

  • 首先,將最小堆中的最小節點拷貝一份並賦值給left,然後重塑最小堆(將最小節點和後面的節點交換位置,接著將”交換位置後的最小節點”之前的全部元素重新構造成最小堆);
  • 接著,再將最小堆中的最小節點拷貝一份並將其賦值right,然後再次重塑最小堆;
  • 然後,新建節點parent,並將它作為left和right的父節點;
  • 接著,將parent的資料複製給最小堆中的指定節點。

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

5. 哈夫曼樹的完整原始碼

哈夫曼樹的節點類(HuffmanNode.java)

/**
 * Huffman節點類(Huffman.java的輔助類)
 *
 * @author skywang
 * @date 2014/03/27
 */

public class HuffmanNode implements Comparable, Cloneable {
    protected int key;              // 權值
    protected HuffmanNode left;     // 左孩子
    protected HuffmanNode right;    // 右孩子
    protected HuffmanNode parent;   // 父結點

    protected HuffmanNode(int key, HuffmanNode left, HuffmanNode right, HuffmanNode parent) {
        this.key = key;
        this.left = left;
        this.right = right;
        this.parent = parent;
    }

    @Override
    public Object clone() {
        Object obj=null;

        try {
            obj = (HuffmanNode)super.clone();//Object 中的clone()識別出你要複製的是哪一個物件。    
        } catch(CloneNotSupportedException e) {
            System.out.println(e.toString());
        }

        return obj;    
    }

    @Override
    public int compareTo(Object obj) {
        return this.key - ((HuffmanNode)obj).key;
    }
}

哈夫曼樹的實現檔案(Huffman.java)

/**
 * Huffman樹
 *
 * @author skywang
 * @date 2014/03/27
 */

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

public class Huffman {

    private HuffmanNode mRoot;  // 根結點

    /* 
     * 建立Huffman樹
     *
     * @param 權值陣列
     */
    public Huffman(int a[]) {
        HuffmanNode parent = null;
        MinHeap heap;

        // 建立陣列a對應的最小堆
        heap = new MinHeap(a);

        for(int i=0; i<a.length-1; i++) {   
            HuffmanNode left = heap.dumpFromMinimum();  // 最小節點是左孩子
            HuffmanNode right = heap.dumpFromMinimum(); // 其次才是右孩子

            // 新建parent節點,左右孩子分別是left/right;
            // parent的大小是左右孩子之和
            parent = new HuffmanNode(left.key+right.key, left, right, null);
            left.parent = parent;
            right.parent = parent;

            // 將parent節點資料拷貝到"最小堆"中
            heap.insert(parent);
        }

        mRoot = parent;

        // 銷燬最小堆
        heap.destroy();
    }

    /*
     * 前序遍歷"Huffman樹"
     */
    private void preOrder(HuffmanNode tree) {
        if(tree != null) {
            System.out.print(tree.key+" ");
            preOrder(tree.left);
            preOrder(tree.right);
        }
    }

    public void preOrder() {
        preOrder(mRoot);
    }

    /*
     * 中序遍歷"Huffman樹"
     */
    private void inOrder(HuffmanNode tree) {
        if(tree != null) {
            inOrder(tree.left);
            System.out.print(tree.key+" ");
            inOrder(tree.right);
        }
    }

    public void inOrder() {
        inOrder(mRoot);
    }


    /*
     * 後序遍歷"Huffman樹"
     */
    private void postOrder(HuffmanNode tree) {
        if(tree != null)
        {
            postOrder(tree.left);
            postOrder(tree.right);
            System.out.print(tree.key+" ");
        }
    }

    public void postOrder() {
        postOrder(mRoot);
    }

    /*
     * 銷燬Huffman樹
     */
    private void destroy(HuffmanNode tree) {
        if (tree==null)
            return ;

        if (tree.left != null)
            destroy(tree.left);
        if (tree.right != null)
            destroy(tree.right);

        tree=null;
    }

    public void destroy() {
        destroy(mRoot);
        mRoot = null;
    }

    /*
     * 列印"Huffman樹"
     *
     * key        -- 節點的鍵值 
     * direction  --  0,表示該節點是根節點;
     *               -1,表示該節點是它的父結點的左孩子;
     *                1,表示該節點是它的父結點的右孩子。
     */
    private void print(HuffmanNode tree, int key, int direction) {

        if(tree != null) {

            if(direction==0)    // tree是根節點
                System.out.printf("%2d is root\n", tree.key);
            else                // tree是分支節點
                System.out.printf("%2d is %2d's %6s child\n", tree.key, key, direction==1?"right" : "left");

            print(tree.left, tree.key, -1);
            print(tree.right,tree.key,  1);
        }
    }

    public void print() {
        if (mRoot != null)
            print(mRoot, mRoot.key, 0);
    }
}

哈夫曼樹對應的最小堆(MinHeap.java)

/**
 * 最小堆(Huffman.java的輔助類)
 *
 * @author skywang
 * @date 2014/03/27
 */

import java.util.ArrayList;
import java.util.List;

public class MinHeap {

    private List<HuffmanNode> mHeap;        // 存放堆的陣列

    /* 
     * 建立最小堆
     *
     * 引數說明:
     *     a -- 資料所在的陣列
     */
    protected MinHeap(int a[]) {
        mHeap = new ArrayList<HuffmanNode>();
        // 初始化陣列
        for(int i=0; i<a.length; i++) {
            HuffmanNode node = new HuffmanNode(a[i], null, null, null);
            mHeap.add(node);
        }

        // 從(size/2-1) --> 0逐次遍歷。遍歷之後,得到的陣列實際上是一個最小堆。
        for (int i = a.length / 2 - 1; i >= 0; i--)
            filterdown(i, a.length-1);
    }

    /* 
     * 最小堆的向下調整演算法
     *
     * 注:陣列實現的堆中,第N個節點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
     *
     * 引數說明:
     *     start -- 被下調節點的起始位置(一般為0,表示從第1個開始)
     *     end   -- 截至範圍(一般為陣列中最後一個元素的索引)
     */
    protected void filterdown(int start, int end) {
        int c = start;      // 當前(current)節點的位置
        int l = 2*c + 1;    // 左(left)孩子的位置
        HuffmanNode tmp = mHeap.get(c); // 當前(current)節點

        while(l <= end) {
            // "l"是左孩子,"l+1"是右孩子
            if(l < end && (mHeap.get(l).compareTo(mHeap.get(l+1))>0))
                l++;        // 左右兩孩子中選擇較小者,即mHeap[l+1]

            int cmp = tmp.compareTo(mHeap.get(l));
            if(cmp <= 0)
                break;      //調整結束
            else {
                mHeap.set(c, mHeap.get(l));
                c = l;
                l = 2*l + 1;   
            }       
        }   
        mHeap.set(c, tmp);
    }

    /*
     * 最小堆的向上調整演算法(從start開始向上直到0,調整堆)
     *
     * 注:陣列實現的堆中,第N個節點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
     *
     * 引數說明:
     *     start -- 被上調節點的起始位置(一般為陣列中最後一個元素的索引)
     */
    protected void filterup(int start) {
        int c = start;          // 當前節點(current)的位置
        int p = (c-1)/2;        // 父(parent)結點的位置 
        HuffmanNode tmp = mHeap.get(c); // 當前(current)節點

        while(c > 0) {
            int cmp = mHeap.get(p).compareTo(tmp);
            if(cmp <= 0)
                break;
            else {
                mHeap.set(c, mHeap.get(p));
                c = p;
                p = (p-1)/2;   
            }       
        }
        mHeap.set(c, tmp);
    } 

    /* 
     * 將node插入到二叉堆中
     */
    protected void insert(HuffmanNode node) {
        int size = mHeap.size();

        mHeap.add(node);    // 將"陣列"插在表尾
        filterup(size);     // 向上調整堆
    }

    /*
     * 交換兩個HuffmanNode節點的全部資料
     */
    private void swapNode(int i, int j) {
        HuffmanNode tmp = mHeap.get(i);
        mHeap.set(i, mHeap.get(j));
        mHeap.set(j, tmp);
    }

    /* 
     * 新建一個節點,並將最小堆中最小節點的資料複製給該節點。
     * 然後除最小節點之外的資料重新構造成最小堆。
     *
     * 返回值:
     *     失敗返回null。
     */
    protected HuffmanNode dumpFromMinimum() {
        int size = mHeap.size();

        // 如果"堆"已空,則返回
        if(size == 0)
            return null;

        // 將"最小節點"克隆一份,將克隆得到的物件賦值給node
        HuffmanNode node = (HuffmanNode)mHeap.get(0).clone();

        // 交換"最小節點"和"最後一個節點"
        mHeap.set(0, mHeap.get(size-1));
        // 刪除最後的元素
        mHeap.remove(size-1);

        if (mHeap.size() > 1)
            filterdown(0, mHeap.size()-1);

        return node;
    }

    // 銷燬最小堆
    protected void destroy() {
        mHeap.clear();
        mHeap = null;
    }
}

哈夫曼樹的測試程式(HuffmanTest.java)

/**
 * Huffman樹的測試程式
 *
 * @author skywang
 * @date 2014/03/27
 */

public class HuffmanTest {

    private static final int a[]= {5,6,8,7,15};

    public static void main(String[] args) {
        int i;
        Huffman tree;

        System.out.print("== 新增陣列: ");
        for(i=0; i<a.length; i++) 
            System.out.print(a[i]+" ");

        // 建立陣列a對應的Huffman樹
        tree = new Huffman(a);

        System.out.print("\n== 前序遍歷: ");
        tree.preOrder();

        System.out.print("\n== 中序遍歷: ");
        tree.inOrder();

        System.out.print("\n== 後序遍歷: ");
        tree.postOrder();
        System.out.println();

        System.out.println("== 樹的詳細資訊: ");
        tree.print();

        // 銷燬二叉樹
        tree.destroy();
    }
}

資料結構與演算法