1. 程式人生 > >數據結構——哈夫曼樹

數據結構——哈夫曼樹

向上 重點 ble reorder 子節點 please pre 哈夫曼 .html

轉自:http://www.cnblogs.com/skywang12345/p/3706833.html

哈夫曼樹的介紹

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

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

技術分享圖片

(01) 路徑和路徑長度

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

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

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

(03) 樹的帶權路徑長度

定義:樹的帶權路徑長度規定為所有葉子結點的帶權路徑長度之和,記為WPL。
例子:示例中,樹的WPL= 1*100 + 2*50 + 3*20 + 3*10 = 100 + 100 + 60 + 30 = 290。


比較下面兩棵樹

技術分享圖片

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

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

左邊的樹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. 基本定義

    public class HuffmanNode : IComparable, ICloneable
    {
        public int key;              // 權值
        public HuffmanNode left;     // 左孩子
        public HuffmanNode right;    // 右孩子
        public HuffmanNode parent;   // 父結點

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

        public object Clone()
        {
            object obj = null;
            obj = this;
            return obj;
        }

        public int CompareTo(object obj)
        {
            return this.key - ((HuffmanNode)obj).key;
        }
    }

HuffmanNode是哈夫曼樹的節點類。

public class Huffman {

    private HuffmanNode mRoot;  // 根結點

    ...
}

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

2. 構造哈夫曼樹

    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)
            {
                Console.Write(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);
                Console.Write(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);
                Console.Write(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是根節點
                    Console.WriteLine("{0} is root\n", tree.key);
                else                // tree是分支節點
                    Console.WriteLine("{0} is {1}‘s {2} 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);
        }
    }

首先創建最小堆,然後進入for循環。

每次循環時:

(01) 首先,將最小堆中的最小節點拷貝一份並賦值給left,然後重塑最小堆(將最小節點和後面的節點交換位置,接著將"交換位置後的最小節點"之前的全部元素重新構造成最小堆);
(02) 接著,再將最小堆中的最小節點拷貝一份並將其賦值right,然後再次重塑最小堆;
(03) 然後,新建節點parent,並將它作為left和right的父節點;
(04) 接著,將parent的數據復制給最小堆中的指定節點。

MinHeap

    public class MinHeap
    {

        private List<HuffmanNode> mHeap;        // 存放堆的數組

        /* 
         * 創建最小堆
         *
         * 參數說明:
         *     a -- 數據所在的數組
         */
        public MinHeap(int[] a)
        {
            mHeap = new List<HuffmanNode>();
            //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   -- 截至範圍(一般為數組中最後一個元素的索引)
         */
        public void filterdown(int start, int end)
        {
            int c = start;      // 當前(current)節點的位置
            int l = 2 * c + 1;  // 左(left)孩子的位置
            HuffmanNode tmp = mHeap[c]; // 當前(current)節點

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

                int cmp = tmp.CompareTo(mHeap[l]);
                if (cmp <= 0)
                    break;      //調整結束
                else
                {
                    mHeap[c] = mHeap[l];
                    c = l;
                    l = 2 * l + 1;
                }
            }
            mHeap[c] = tmp;
        }

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

            while (c > 0)
            {
                int cmp = mHeap[p].CompareTo(tmp);
                if (cmp <= 0)
                    break;
                else
                {
                    mHeap[c] = mHeap[p];
                    c = p;
                    p = (p - 1) / 2;
                }
            }
            mHeap[c] = tmp;
        }

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

            mHeap.Add(node);    // 將"數組"插在表尾
            filterup(size);     // 向上調整堆
        }

        /*
         * 交換兩個HuffmanNode節點的全部數據
         */
        private void swapNode(int i, int j)
        {
            HuffmanNode tmp = mHeap[i];
            mHeap[i] = mHeap[j];
            mHeap[j] = tmp;
        }

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

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

            // 將"最小節點"克隆一份,將克隆得到的對象賦值給node
            HuffmanNode node = (HuffmanNode)mHeap[0].Clone();

            // 交換"最小節點"和"最後一個節點"
            mHeap[0] = mHeap[size - 1];
            // 刪除最後的元素
            mHeap.Remove(mHeap[size - 1]);

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

            return node;
        }

        // 銷毀最小堆
        public void destroy()
        {
            mHeap.Clear();
            mHeap = null;
        }
    }

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

測試程序
    public class HuffmanTest
    {
        private int[] a = { 5, 6, 8, 7, 15 };

        public void main()
        {
            int i;
            Huffman tree;

            Console.WriteLine("== 添加數組: ");
            for (i = 0; i < a.Length; i++)
                Console.WriteLine(a[i] + " ");

            // 創建數組a對應的Huffman樹
            tree = new Huffman(a);

            Console.WriteLine("\n== 前序遍歷: ");
            tree.preOrder();

            Console.WriteLine("\n== 中序遍歷: ");
            tree.inOrder();

            Console.WriteLine("\n== 後序遍歷: ");
            tree.postOrder();


            Console.WriteLine("== 樹的詳細信息: ");
            tree.print();

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

結果

== 添加數組:
5
6
8
7
15

== 前序遍歷:
41 15 7 8 26 11 5 6 15
== 中序遍歷:
7 15 8 41 5 11 6 26 15
== 後序遍歷:
7 8 15 5 6 11 15 26 41 == 樹的詳細信息:
41 is root

15 is 41‘s left child

7 is 15‘s left child

8 is 15‘s right child

26 is 41‘s right child

11 is 26‘s left child

5 is 11‘s left child

6 is 11‘s right child

15 is 26‘s right child

數據結構——哈夫曼樹