深度解析(十五)哈夫曼树
哈夫曼樹(一)之 C語言詳解
本章介紹哈夫曼樹。和以往一樣,本文會先對哈夫曼樹的理論知識進行簡單介紹,然后給出C語言的實現(xiàn)。后續(xù)再分別給出C++和Java版本的實現(xiàn);實現(xiàn)的語言雖不同,但是原理如出一轍,選擇其中之一進行了解即可。若文章有錯誤或不足的地方,請幫忙指出!
目錄
1. 哈夫曼樹的介紹
2. 哈夫曼樹的圖文解析
3. 哈夫曼樹的基本操作
4. 哈夫曼樹的完整源碼
轉(zhuǎn)載請注明出處:http://www.cnblogs.com/skywang12345/
更多內(nèi)容:數(shù)據(jù)結(jié)構(gòu)與算法系列 目錄
哈夫曼樹的介紹
Huffman Tree,中文名是哈夫曼樹或霍夫曼樹,它是最優(yōu)二叉樹。
定義:給定n個權(quán)值作為n個葉子結(jié)點,構(gòu)造一棵二叉樹,若樹的帶權(quán)路徑長度達到最小,則這棵樹被稱為哈夫曼樹。 這個定義里面涉及到了幾個陌生的概念,下面就是一顆哈夫曼樹,我們來看圖解答。
(01) 路徑和路徑長度
定義:在一棵樹中,從一個結(jié)點往下可以達到的孩子或?qū)O子結(jié)點之間的通路,稱為路徑。通路中分支的數(shù)目稱為路徑長度。若規(guī)定根結(jié)點的層數(shù)為1,則從根結(jié)點到第L層結(jié)點的路徑長度為L-1。
例子:100和80的路徑長度是1,50和30的路徑長度是2,20和10的路徑長度是3。
(02) 結(jié)點的權(quán)及帶權(quán)路徑長度
定義:若將樹中結(jié)點賦給一個有著某種含義的數(shù)值,則這個數(shù)值稱為該結(jié)點的權(quán)。結(jié)點的帶權(quán)路徑長度為:從根結(jié)點到該結(jié)點之間的路徑長度與該結(jié)點的權(quán)的乘積。
例子:節(jié)點20的路徑長度是3,它的帶權(quán)路徑長度= 路徑長度 * 權(quán) = 3 * 20 = 60。
(03) 樹的帶權(quán)路徑長度
定義:樹的帶權(quán)路徑長度規(guī)定為所有葉子結(jié)點的帶權(quán)路徑長度之和,記為WPL。
例子:示例中,樹的WPL= 1*100 + 2*80 + 3*20 + 3*10 = 100 + 160 + 60 + 30 = 350。
比較下面兩棵樹
上面的兩棵樹都是以{10, 20, 50, 100}為葉子節(jié)點的樹。
左邊的樹WPL=2*10 + 2*20 + 2*50 + 2*100 = 360
右邊的樹WPL=350
左邊的樹WPL > 右邊的樹的WPL。你也可以計算除上面兩種示例之外的情況,但實際上右邊的樹就是{10,20,50,100}對應(yīng)的哈夫曼樹。至此,應(yīng)該堆哈夫曼樹的概念有了一定的了解了,下面看看如何去構(gòu)造一棵哈夫曼樹。
哈夫曼樹的圖文解析
假設(shè)有n個權(quán)值,則構(gòu)造出的哈夫曼樹有n個葉子結(jié)點。 n個權(quán)值分別設(shè)為 w1、w2、…、wn,哈夫曼樹的構(gòu)造規(guī)則為:
1. 將w1、w2、…,wn看成是有n 棵樹的森林(每棵樹僅有一個結(jié)點);
2. 在森林中選出根結(jié)點的權(quán)值最小的兩棵樹進行合并,作為一棵新樹的左、右子樹,且新樹的根結(jié)點權(quán)值為其左、右子樹根結(jié)點權(quán)值之和;
3. 從森林中刪除選取的兩棵樹,并將新樹加入森林;
4. 重復(fù)(02)、(03)步,直到森林中只剩一棵樹為止,該樹即為所求得的哈夫曼樹。
以{5,6,7,8,15}為例,來構(gòu)造一棵哈夫曼樹。
第1步:創(chuàng)建森林,森林包括5棵樹,這5棵樹的權(quán)值分別是5,6,7,8,15。
第2步:在森林中,選擇根節(jié)點權(quán)值最小的兩棵樹(5和6)來進行合并,將它們作為一顆新樹的左右孩子(誰左誰右無關(guān)緊要,這里,我們選擇較小的作為左孩子),并且新樹的權(quán)值是左右孩子的權(quán)值之和。即,新樹的權(quán)值是11。 然后,將"樹5"和"樹6"從森林中刪除,并將新的樹(樹11)添加到森林中。
第3步:在森林中,選擇根節(jié)點權(quán)值最小的兩棵樹(7和8)來進行合并。得到的新樹的權(quán)值是15。 然后,將"樹7"和"樹8"從森林中刪除,并將新的樹(樹15)添加到森林中。
第4步:在森林中,選擇根節(jié)點權(quán)值最小的兩棵樹(11和15)來進行合并。得到的新樹的權(quán)值是26。 然后,將"樹11"和"樹15"從森林中刪除,并將新的樹(樹26)添加到森林中。
第5步:在森林中,選擇根節(jié)點權(quán)值最小的兩棵樹(15和26)來進行合并。得到的新樹的權(quán)值是41。 然后,將"樹15"和"樹26"從森林中刪除,并將新的樹(樹41)添加到森林中。
此時,森林中只有一棵樹(樹41)。這棵樹就是我們需要的哈夫曼樹!
哈夫曼樹的基本操作
哈夫曼樹的重點是如何構(gòu)造哈夫曼樹。本文構(gòu)造哈夫曼時,用到了以前介紹過的"(二叉堆)最小堆"。下面對哈夫曼樹進行講解。
1. 基本定義
typedef int Type;typedef struct _HuffmanNode {Type key; // 權(quán)值struct _HuffmanNode *left; // 左孩子struct _HuffmanNode *right; // 右孩子struct _HuffmanNode *parent; // 父節(jié)點 } HuffmanNode, *HuffmanTree;HuffmanNode是哈夫曼樹的節(jié)點類。
2. 構(gòu)造哈夫曼樹
/** 創(chuàng)建Huffman樹** 參數(shù)說明:* a 權(quán)值數(shù)組* size 數(shù)組大小** 返回值:* Huffman樹的根*/ HuffmanNode* create_huffman(Type a[], int size) {int i;HuffmanNode *left, *right, *parent;// 建立數(shù)組a對應(yīng)的最小堆create_minheap(a, size);for(i=0; i<size-1; i++){ left = dump_from_minheap(); // 最小節(jié)點是左孩子right = dump_from_minheap(); // 其次才是右孩子// 新建parent節(jié)點,左右孩子分別是left/right;// parent的大小是左右孩子之和parent = huffman_create_node(left->key+right->key, left, right, NULL);left->parent = parent;right->parent = parent;// 將parent節(jié)點數(shù)據(jù)拷貝到"最小堆"中if (dump_to_minheap(parent)!=0){printf("插入失敗!\n結(jié)束程序\n");destroy_huffman(parent);parent = NULL;break;}} // 銷毀最小堆destroy_minheap();return parent; }首先通過create_huffman(a, size)來一個最小堆。最小堆構(gòu)造完成之后,進入for循環(huán)。
每次循環(huán)時:
(01) 首先,將最小堆中的最小節(jié)點拷貝一份并賦值給left,然后重塑最小堆(將最小節(jié)點和后面的節(jié)點交換位置,接著將"交換位置后的最小節(jié)點"之前的全部元素重新構(gòu)造成最小堆);
(02) 接著,再將最小堆中的最小節(jié)點拷貝一份并將其賦值right,然后再次重塑最小堆;
(03) 然后,新建節(jié)點parent,并將它作為left和right的父節(jié)點;
(04) 接著,將parent的數(shù)據(jù)復(fù)制給最小堆中的指定節(jié)點。
在二叉堆中已經(jīng)介紹過堆,這里就不再對堆的代碼進行說明了。若有疑問,直接參考后文的源碼。其它的相關(guān)代碼,也Please RTFSC(Read The Fucking Source Code)!
哈夫曼樹的完整源碼
哈夫曼樹的源碼共包括4個文件。
1. 哈夫曼樹的頭文件(huffman.h)
2. 哈夫曼樹的實現(xiàn)文件(huffman.c)
3. 哈夫曼樹對應(yīng)的最小堆(minheap.c)
4. 哈夫曼樹的測試程序(huffman_test.c)
?
1. huffman.h
#ifndef _AVL_TREE_H_
#define _AVL_TREE_H_
typedef int Type;
typedef struct _HuffmanNode{
?? ?Type key;?? ??? ??? ??? ??? ?/* 權(quán)值*/
?? ?struct _HuffmanNode *left;?? ?/* 左孩子*/
?? ?struct _HuffmanNode *right;?? ?/* 右孩子*/
?? ?struct _HuffmanNode *parent;/* 父節(jié)點*/
}HuffmanNode, *HuffmanTree;
/* 前序遍歷"Huffman樹"*/
void preorder_huffman(HuffmanTree tree);
/* 中序遍歷"Huffman樹"*/
void inorder_huffman(HuffmanTree tree);
/* 后序遍歷"Huffman樹"*/
void postorder_huffman(HuffmanTree tree);
/* 創(chuàng)建Huffman樹*/
HuffmanNode* create_huffman(Type arr[], int size);
/* 銷毀Huffman樹*/
void destroy_huffman(HuffmanTree tree);
/* 打印Huffman樹*/
void print_huffman(HuffmanTree tree);
#endif
2. huffman.c
/**
?* Huffman樹(C語言): C語言實現(xiàn)的Huffman樹。
?*
?* 構(gòu)造Huffman樹時,使用到了最小堆。
?*
?* @author skywang
?* @date 2014/03/25
?*/
#include <stdio.h>
#include <stdlib.h>
#include "huffman.h"
// 創(chuàng)建最小堆
extern void create_minheap(Type a[], int size);
// 新建一個節(jié)點,并將最小堆中最小節(jié)點的數(shù)據(jù)復(fù)制給該節(jié)點。
extern HuffmanNode* dump_from_minheap();
// 將data插入到二叉堆中。0表示成功,-1表示失敗。
extern int dump_to_minheap(HuffmanNode *node);
// 銷毀最小堆
extern void destroy_minheap();
/*
?* 前序遍歷"Huffman樹"
?*/
void preorder_huffman(HuffmanTree tree)
{
??? if(tree != NULL)
??? {
??????? printf("%d ", tree->key);
??????? preorder_huffman(tree->left);
??????? preorder_huffman(tree->right);
??? }
}
/*
?* 中序遍歷"Huffman樹"
?*/
void inorder_huffman(HuffmanTree tree)
{
??? if(tree != NULL)
??? {
??????? inorder_huffman(tree->left);
??????? printf("%d ", tree->key);
??????? inorder_huffman(tree->right);
??? }
}
/*
?* 后序遍歷"Huffman樹"
?*/
void postorder_huffman(HuffmanTree tree)
{
??? if(tree != NULL)
??? {
??????? postorder_huffman(tree->left);
??????? postorder_huffman(tree->right);
??????? printf("%d ", tree->key);
??? }
}
/*
?* 創(chuàng)建Huffman樹結(jié)點。
?*
?* 參數(shù)說明:
?*???? key 是鍵值。
?*???? left 是左孩子。
?*???? right 是右孩子。
?*???? parent 是父節(jié)點
?*/
HuffmanNode* huffman_create_node(Type key, HuffmanNode *left, HuffmanNode* right, HuffmanNode* parent)
{
??? HuffmanNode* p;
??? if ((p = (HuffmanNode *)malloc(sizeof(HuffmanNode))) == NULL)
??????? return NULL;
??? p->key = key;
??? p->left = left;
??? p->right = right;
??? p->parent = parent;
??? return p;
}
/*
?* 創(chuàng)建Huffman樹
?*
?* 參數(shù)說明:
?*???? a 權(quán)值數(shù)組
?*???? size 數(shù)組大小
?*
?* 返回值:
?*???? Huffman樹的根
?*/
HuffmanNode* create_huffman(Type a[], int size)
{
??? int i;
??? HuffmanNode *left, *right, *parent;
?? ?// 建立數(shù)組a對應(yīng)的最小堆
??? create_minheap(a, size);
?
??? for(i=0; i<size-1; i++)
??? {? ?
??????? left = dump_from_minheap();? // 最小節(jié)點是左孩子
??????? right = dump_from_minheap(); // 其次才是右孩子
?
?? ??? ?// 新建parent節(jié)點,左右孩子分別是left/right;
?? ??? ?// parent的大小是左右孩子之和
??????? parent = huffman_create_node(left->key+right->key, left, right, NULL);
??????? left->parent = parent;
?? ??? ?right->parent = parent;
?
?? ??? ?// 將parent節(jié)點數(shù)據(jù)拷貝到"最小堆"中
?? ??? ?if (dump_to_minheap(parent)!=0)
?? ??? ?{
?? ??? ??? ?printf("插入失敗!\n結(jié)束程序\n");
?? ??? ??? ?destroy_huffman(parent);
?? ??? ??? ?parent = NULL;
?? ??? ??? ?break;
?? ??? ?}
??? }? ?
?? ?// 銷毀最小堆
?? ?destroy_minheap();
?? ?return parent;
}
/*
?* 銷毀Huffman樹
?*/
void destroy_huffman(HuffmanTree tree)
{
??? if (tree==NULL)
??????? return ;
??? if (tree->left != NULL)
??????? destroy_huffman(tree->left);
??? if (tree->right != NULL)
??????? destroy_huffman(tree->right);
??? free(tree);
}
/*
?* 打印"Huffman樹"
?*
?* tree?????? -- Huffman樹的節(jié)點
?* key??????? -- 節(jié)點的鍵值
?* direction? --? 0,表示該節(jié)點是根節(jié)點;
?*?????????????? -1,表示該節(jié)點是它的父結(jié)點的左孩子;
?*??????????????? 1,表示該節(jié)點是它的父結(jié)點的右孩子。
?*/
void huffman_print(HuffmanTree tree, Type key, int direction)
{
??? if(tree != NULL)
??? {
??????? if(direction==0)??? // tree是根節(jié)點
??????????? printf("%2d is root\n", tree->key, key);
??????? else??????????????? // tree是分支節(jié)點
??????????? printf("%2d is %2d's %6s child\n", tree->key, key, direction==1?"right" : "left");
??????? huffman_print(tree->left, tree->key, -1);
??????? huffman_print(tree->right,tree->key,? 1);
??? }
}
void print_huffman(HuffmanTree tree)
{
?? ?if (tree!=NULL)
?? ??? ?huffman_print(tree, tree->key, 0);
}
3. minheap.c
/**
?* 最小堆:為Huffman樹服務(wù)的。
?*
?* @author skywang
?* @date 2014/03/25
?*/
#include <stdio.h>
#include <stdlib.h>
#include "huffman.h"
static HuffmanNode *m_heap;?? ?// 最小堆的數(shù)組
static int m_capacity;?? ??? ?// 總的容量
static int m_size;?? ??? ??? ?// 當(dāng)前有效數(shù)據(jù)的數(shù)量
?
/*
?* 最小堆的向下調(diào)整算法
?*
?* 注:數(shù)組實現(xiàn)的堆中,第N個節(jié)點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
?*
?* 參數(shù)說明:
?*???? start -- 被下調(diào)節(jié)點的起始位置(一般為0,表示從第1個開始)
?*???? end?? -- 截至范圍(一般為數(shù)組中最后一個元素的索引)
?*/
static void minheap_filterdown(int start, int end)
{
??? int c = start; ?? ? ?? ?// 當(dāng)前(current)節(jié)點的位置
??? int l = 2*c + 1; ?? ?// 左(left)孩子的位置
??? HuffmanNode tmp = m_heap[c];?? ?// 當(dāng)前(current)節(jié)點
??? while(l <= end)
??? {
?? ??? ?// "l"是左孩子,"l+1"是右孩子
??????? if(l < end && m_heap[l].key > m_heap[l+1].key)
??????????? l++;?? ??? ?// 左右兩孩子中選擇較小者,即m_heap[l+1]
??????? if(tmp.key <= m_heap[l].key)
??????????? break;?? ??? ?//調(diào)整結(jié)束
??????? else
??????? {
??????????? m_heap[c] = m_heap[l];
??????????? c = l;
??????????? l = 2*l + 1;? ?
??????? }????? ?
??? }? ?
??? m_heap[c] = tmp;
}
?
/*
?* 最小堆的向上調(diào)整算法(從start開始向上直到0,調(diào)整堆)
?*
?* 注:數(shù)組實現(xiàn)的堆中,第N個節(jié)點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
?*
?* 參數(shù)說明:
?*???? start -- 被上調(diào)節(jié)點的起始位置(一般為數(shù)組中最后一個元素的索引)
?*/
static void filter_up(int start)
{
??? int c = start;?? ??? ??? ?// 當(dāng)前節(jié)點(current)的位置
??? int p = (c-1)/2;?? ??? ?// 父(parent)結(jié)點的位置
??? HuffmanNode tmp = m_heap[c];?? ??? ?// 當(dāng)前節(jié)點(current)
??? while(c > 0)
??? {
??????? if(m_heap[p].key <= tmp.key)
??????????? break;
??????? else
??????? {
??????????? m_heap[c] = m_heap[p];
??????????? c = p;
??????????? p = (p-1)/2;? ?
??????? }????? ?
??? }
??? m_heap[c] = tmp;
}
?
/*
?* 將node插入到二叉堆中
?*
?* 返回值:
?*???? 0,表示成功
?*??? -1,表示失敗
?*/
int dump_to_minheap(HuffmanNode *node)
{
??? // 如果"堆"已滿,則返回
??? if(m_size == m_capacity)
??????? return -1;
?
??? m_heap[m_size] = *node;???? // 將"node的數(shù)據(jù)"全部復(fù)制到"數(shù)組末尾"
??? filter_up(m_size);?? ??? ??? ?// 向上調(diào)整堆
??? m_size++;?? ??? ??? ??? ??? ?// 堆的實際容量+1
??? return 0;
}
/*
?* 交換兩個HuffmanNode節(jié)點的全部數(shù)據(jù)
?*/
static void swap_node(int i, int j)
{
?? ?HuffmanNode tmp = m_heap[i];
?? ?m_heap[i] = m_heap[j];
?? ?m_heap[j] = tmp;
}
/*
?* 新建一個節(jié)點,并將最小堆中最小節(jié)點的數(shù)據(jù)復(fù)制給該節(jié)點。
?* 然后除最小節(jié)點之外的數(shù)據(jù)重新構(gòu)造成最小堆。
?*
?* 返回值:
?*???? 失敗返回NULL。
?*/
HuffmanNode* dump_from_minheap()
{
??? // 如果"堆"已空,則返回
??? if(m_size == 0)
?? ??? ?return NULL;
?? ?HuffmanNode *node;
??? if((node = (HuffmanNode *)malloc(sizeof(HuffmanNode))) == NULL)
?? ??? ?return NULL;
?? ?// 將"最小節(jié)點的全部數(shù)據(jù)"復(fù)制給node
?? ?*node = m_heap[0];
?? ?swap_node(0, m_size-1);?? ??? ??? ??? ?// 交換"最小節(jié)點"和"最后一個節(jié)點"
??? minheap_filterdown(0, m_size-2);?? ?// 將m_heap[0...m_size-2]構(gòu)造成一個最小堆
?? ?m_size--;?? ??? ??? ??? ??? ??? ?
?? ?return node;
}
/*
?* 打印二叉堆
?*
?* 返回值:
?*???? 0,表示成功
?*??? -1,表示失敗
?*/
void minheap_print()
{
?? ?int i;
?? ?for (i=0; i<m_size; i++)
?? ??? ?printf("%d ", m_heap[i].key);
}
/*
?* 創(chuàng)建最小堆
?*
?* 參數(shù)說明:
?*???? a -- 數(shù)據(jù)所在的數(shù)組
?*???? size -- 數(shù)組大小
?*/
void create_minheap(Type a[], int size)
{
?? ?int i;
?? ?// 創(chuàng)建最小堆所對應(yīng)的數(shù)組
?? ?m_size = size;
?? ?m_capacity = size;
?? ?m_heap = (HuffmanNode *)malloc(sizeof(HuffmanNode)*size);
?? ?
?? ?// 初始化數(shù)組
??? for(i=0; i<size; i++)
?? ?{
?? ??? ?m_heap[i].key = a[i];
?? ??? ?m_heap[i].parent = m_heap[i].left = m_heap[i].right = NULL;
?? ?}
??? // 從(size/2-1) --> 0逐次遍歷。遍歷之后,得到的數(shù)組實際上是一個最小堆。
??? for (i = size / 2 - 1; i >= 0; i--)
?? ??? ?minheap_filterdown(i, size-1);
}
// 銷毀最小堆
void destroy_minheap()
{
?? ?m_size = 0;
?? ?m_capacity = 0;
?? ?free(m_heap);
}
4. huffman_test.c
/**
?* C 語言: Huffman樹
?*
?* @author skywang
?* @date 2014/03/25
?*/
#include <stdio.h>
#include "huffman.h"
#define LENGTH(a) ( (sizeof(a)) / (sizeof(a[0])) )
void main()
{
?? ?int a[]= {5,6,8,7,15};
?? ?int i,ilen=LENGTH(a);
?? ?HuffmanTree root=NULL;
?? ?printf("== 添加數(shù)組: ");
?? ?for(i=0; i<ilen; i++)
?? ??? ?printf("%d ", a[i]);
?? ?// 創(chuàng)建數(shù)組a對應(yīng)的Huffman樹
?? ?root = create_huffman(a, ilen);
?? ?printf("\n== 前序遍歷: ");
?? ?preorder_huffman(root);
?? ?printf("\n== 中序遍歷: ");
?? ?inorder_huffman(root);
?? ?printf("\n== 后序遍歷: ");
?? ?postorder_huffman(root);
?? ?printf("\n");
?? ?printf("== 樹的詳細(xì)信息: \n");
?? ?print_huffman(root);
?? ?// 銷毀二叉樹
?? ?destroy_huffman(root);
}
總結(jié)
以上是生活随笔為你收集整理的深度解析(十五)哈夫曼树的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vim末行模式下相关操作+配置文件
- 下一篇: 给Ocelot做一个Docker 镜像