【演算法基礎】----貪心演算法的應用之Huffman編碼
1.Huffman樹的基礎概念
路徑:從樹中的一個節點到另一個節點之間的分支構成這兩個節點的路徑。
路徑長度:路徑上分支的數目。
樹的路徑高度:從根到每一個節點的路徑之和。
節點的帶權路徑長度:從該節點到樹根之間的路徑長度與節點上權的乘積。
樹的帶權路徑長度:樹中所有葉節點的帶權路徑長度之和,通常記作WPL(Weighted Path Length of Tree)。
Huffman樹:假設有n個權值{m1,m2,m3,,,,,mn},可以構造一棵具有n個葉子節點的二叉樹,每個葉子節點帶權為mi,則其中帶權路徑長度WPL最小的二叉樹稱作Huffman樹。
根據定義,Huffman樹是帶權路徑長度最小的樹,假設一個二叉樹有4個節點,分別是A,B,C,D,其權重分別是5,7,2,13,通過這4個節點可以構成多種二叉樹。
WPL(A)=5*2 + 7*2 + 2*2 + 13*2 = 54
WPL(B)=5*1 + 7*2 + 2*3 + 13*3 = 64
WPL(C)=13*1 + 7*2 + 2*3 + 5*3 = 48
通過上面的例子,其實可以發現的規律是在構造二叉樹的時候,將權值大的節點儘量放在路徑高度小的位置,而將權值小的節點儘量放在路徑高度大的位置,這樣就可以得到最優的二叉樹。
2.Huffman樹的構造過程
(1)將給定的n個權值{m1,m2,m3,,,,,mn}作為n個根節點的權值並構造一棵具有n棵二叉樹的森林{T1,T2,T3,,,,Tn},其中每棵二叉樹都只有一個節點。
(2)在森林中選取兩棵根節點權值最小的二叉樹,作為左右子樹並構造一棵二叉樹,新二叉樹的根節點權值為這兩棵樹根的權值之和。
(3)在森林中,將上面選擇的兩棵權值最小的二叉樹從森林中刪除,同時將上一步中新構造的二叉樹加入到森林中。
(4)重複(2)(3)步驟,直到森林中只有一棵二叉樹為止,這棵就是Huffman樹。
3.Huffman編碼
在現實中,如果要使用ASCII碼來設計電文,會浪費許多的空間,因為所有的編碼都是7位。因此為了節省空間,可以將電文設計成二進位制字首編碼,其實就是以n種字元的出現頻率作為權重,然後設計Huffman樹的過程。正是因為這個原因,所以通常將二進位制字首編碼稱為Huffman編碼。
Huffman樹的資料結構
(1).我們採用陣列的形式來儲存Huffman樹,所以在該結構父節點,左右子樹節點都儲存在陣列對應的下標位置,因此不採用指標變數,還需要定義一個字元數指標,用來指向Huffman編碼字串。
<pre name="code" class="cpp">typedef struct HuffmanTree {
int weight ;
int parent ;
int left ;
int right ;
}HuffmanTree ;
typedef char * HuffmanCode ;
(2).編寫建立Huffman樹的程式碼
<pre name="code" class="cpp">void CreateTree(HuffmanTree *HuffTree , int n , int * w) {
int i , m = 2 * n - 1 ;
int bt1 , bt2 ;
if(n <= 1)
return ;
for(i = 1 ; 1 <= n ; ++ i) {
HuffTree[i].weight = w[i-1] ;
HuffTree[i].parent = 0 ;
HuffTree[i].left = 0 ;
HuffTree[i].right = 0 ;
}
for(;i <= m ; ++ i){
HuffTree[i].weight = 0 ;
HuffTree[i].parent = 0 ;
HuffTree[i].left = 0 ;
HuffTree[i].right = 0 ;
}
for(i = n + 1 ; i < m ; ++ i) {
SelectNode(HuffTree , i -1 , &bt1 , &bt2) ;
HuffTree[bt1].parent = i ;
HuffTree[bt2].parent = i ;
HuffTree[i].left = bt1 ;
HuffTree[i].right = bt2 ;
HuffTree[i].weight = HuffTree[bt1].weight + HuffTree[bt2].weight ;
}
}
其中引數:
HuffTree :指向HuffTree樹的指標,呼叫函式申請記憶體,並得到這個指標
n :建立HuffTree樹的葉節點數量
w :是一個指標,用於傳入n個節點的權值
(3).編寫SelectNode()函式,功能是在建立Huffman樹函式CreateTree()中反覆使用,該函式用於從無父節點中選出2個權值最小節點
void SelectNode(HuffmanTree * HuffTree , int n , int * bt1 , int * bt2) {
//從1-x個節點中選擇parent節點為0,權重最小的兩個節點
int i ;
HuffmanTree *HuffTree1 , *HuffTree2 , *t ;
HuffTree1 = NULL ;
HuffTree2 = NULL ; //初始化兩個節點為空
for(i = 1 ; i <= n ; ++ i) { //迴圈處理1-n個節點
if(HuffTree[i].parent = 0) { //父節點為空
//找到第一個父節點為空的節點,作為HuffTree1
if(HuffTree1 == NULL) {
HuffTree1 = HuffTree + i ; //指向第i個節點
continue ; //繼續迴圈
}
//找到第二個父節點為空的節點,作為HuffTree2
if(HuffTree2 == NULL) { //節點指標2為空
HuffTree2 = HuffTree + i ; //指向第i個節點
if(HuffTree1 ->weight > HuffTree2 ->weight) { //比較兩個節點的權重,使HuffTree1指向的節點權重小
t = HuffTree2 ;
HuffTree2 = HuffTree1 ;
HuffTree1 = t ;
}
continue ;
}
if(HuffTree1 && HuffTree2) { //若HuffTree1,HuffTree2兩個指標都有效
if(HuffTree[i].weight <= HuffTree1 ->weight) { //第i個節點權重小於HuffTree1指向的節點
HuffTree2 = HuffTree1 ; //
HuffTree1 = HuffTree + i ; //
}
else if(HuffTree[i].weight < HuffTree2 ->weight) { //
HuffTree2 = HuffTree + i ; //
}
}
}
}
if(HuffTree1 > HuffTree2) { //增加比較,使二叉樹左側為葉子節點
*bt2 = HuffTree1 - HuffTree ;
*bt1 = HuffTree2 - HuffTree ;
}
else {
*bt2 = HuffTree2 - HuffTree ;
*bt1 = HuffTree1 - HuffTree ;
}
void HuffmanCoding(HuffmanTree *HuffTree , int n , HuffmanCode * HuffCode) {
char * cd ;
int start , i ;
int current , parent ;
cd = (char *)malloc(n * sizeof(char)) ; //
cd[n-1] = '\0' ; //
for(i = 1 ; i <= n ; ++i) {
start = n - 1 ;
current = i ;
parent = HuffTree[current].parent ;
while(parent) {
if(current == HuffTree[parent].left)
cd[--start] = '0' ;
else
cd[--start] = '1' ;
current = parent ;
parent = HuffTree[parent].parent ;
}
HuffCode[i-1] = (char *)malloc((n - start) * sizeof(char)) ;
strcpy(HuffCode[i-1] , & cd[start]) ;
}
free(cd) ;
}
int main(void) {
int i , n = 4 , m ;
char test[] = "DBDBDABDCDADBDADBDADACDBDBD" ;
char code[100] , code1[100] ;
char alphabte[] = { 'A' , 'B' , 'C' , 'D' } ;
int w[] = {5 , 7 , 2 , 13} ;
HuffmanTree *HuffTree ;
HuffmanCode * HuffCode ;
m = 2 * n - 1 ;
HuffTree = (HuffmanTree *)malloc((m + 1) * sizeof(HuffmanTree)) ;
HuffCode = (HuffmanCode *)malloc((n * sizeof(char *))) ;
CreateTree(HuffTree , n , HuffCode) ;
HuffmanCoding(HuffTree , n , HuffCode) ;
for(i = 1 ; i <= n ; ++ i)
printf("字母:%c , 權重:%d , 編碼為:%s\n" , alphabte[i - 1] , HuffTree[i].weight , HuffCode[i - 1]) ;
return 0 ;
}
</pre><pre name="code" class="cpp">
此外,還有一種傳統的樹形結構的方法,優點是較為直觀,能夠形象的模擬出構造Huffman樹的過程,但缺點是較為浪費空間。
#include <stdio.h>
#include <stdlib.h>
typedef struct HuffTree {
char ch ;
int weight ;
struct HuffTree * left ;
struct HuffTree * right ;
}HuffTree ;
void Huffman(HuffTree ** forest , int num) {
int min_1 , min_2 ;
int i , j ;
HuffTree * newNode = NULL ;
for(i = 0 ; i < num -1 ; ++ i) {
//找到第一個沒有父節點的二叉樹
for(min_1 = 0 ; min_1 < num && forest[min_1] == NULL ; min_1 ++) ;
//找到第二個沒有父節點的二叉樹
for(min_2 = min_1 ++ ; min_2 < num && forest[min_2] == NULL ; min_2 ++) ;
//通過一次遍歷找到權值最小和次小的兩個二叉樹
//如果初始情況下,min_1的權值大於min_2,那麼通過第一次比較就可以交換
for(j = min_2 ; j < num ; ++ j) {
//如果當前節點已經有父節點,就跳過
if(forest[j]) {
//如果當前節點比最小的節點權值更小,就把它賦給最小,把原來的最小賦給次小
if(forest[j] ->weight < forest[min_1] -> weight) {
min_2 = min_1 ;
min_1 = j ;
}
//而如果當前節點比最小大,比次小小,就把它賦給次小
else if(forest[j] -> weight < forest[min_2] -> weight) {
min_2 = j ;
}
newNode = (HuffTree *) malloc (sizeof(HuffTree)) ;
newNode ->weight = forest[min_1] -> weight + forest[min_2] -> weight ;
newNode ->left = forest[min_1] ;
newNode ->right = forest[min_2] ;
forest[min_1] = newNode ;
forest[min_2] = NULL ;
}
}
}
}
int main(void) {
int num ;
int i ;
printf("Input the num of codes:") ;
scanf("%d" , num) ;
HuffTree ** forest = (HuffTree **) malloc (num * sizeof(HuffTree *)) ;
//initialization forest
for(i = 0 ; i < num ; ++ i) {
forest[i] = (HuffTree *) malloc (sizeof(HuffTree)) ;
printf("input the No.%d HuffTree's charactor:" , i) ;
scanf("%c" , forest[i] -> ch) ;
printf("input the No.%d HuffTree's charactor:" , i) ;
scanf("%d" , forest[i] -> weight) ;
forest[i] -> right = NULL ;
forest[i] -> left = NULL ;
}
Huffman(forest , num) ;
return 0 ;
}
相關推薦
【演算法基礎】----貪心演算法的應用之Huffman編碼
1.Huffman樹的基礎概念 路徑:從樹中的一個節點到另一個節點之間的分支構成這兩個節點的路徑。 路徑長度:路徑上分支的數目。 樹的路徑高度:從根到每一個節點的路徑之和。 節點的帶權路徑長度:從該節點到樹根之間的路徑長度與節點上權的乘積。 樹的帶權路徑長度:樹中所有葉節點
【演算法導論】貪心演算法之揹包問題
在討論貪心演算法時,我們先了解貪心演算法與動態規劃之間的區別與聯絡,後面我們將發現可以用0、1揹包問題和部分揹包問題來比較貪心演算法和動態規劃的關係。 我們知道,對於一個最優解問題,貪心演算法不一定能夠產生一個最優解。因為,如果想要採用貪心演
【Java基礎】RTTI與反射之Java
start auth try dword star sse from tac sed 1 ; Example assembly language program -- 2 ; Author: Karllen 3 ; Date: revised 05/2014
【演算法基礎】動態規劃解題例項之野營問題
問題描述: 假設你要去野營。你有一個容量為6磅的揹包,需要決定該攜帶下面的哪些東西。其中每樣東西都有相應的價值,價值越大意味著越重要: 水(重3磅,價值10) 書(重1磅,價值3) 食物(重2磅,價值9) 夾克(重2磅,價值5) 相機(重1磅,價值
【資料結構】順序表應用1:多餘元素刪除之移位演算法
Problem Description 一個長度不超過10000資料的順序表,可能存在著一些值相同的“多餘”資料元素(型別為整型),編寫一個程式將“多餘”的資料元素從順序表中刪除,使該表由一個“非純表
【OJ.3326】順序表應用3:元素位置互換之移位演算法
順序表應用3:元素位置互換之移位演算法 Time Limit: 1
【演算法】貪心演算法
1. 定義 貪心演算法(又稱貪婪演算法)是指,在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,他所做出的是在某種意義上的區域性最優解。 2. 基本要素 (1)貪心選擇 貪心選擇是指所求問題的整體最優解可以通過一系列區域性最優的選擇,即貪心選擇
【演算法基礎】字串的全排列演算法
題目描述 輸入一個字串,按字典序打印出該字串中字元的所有排列。例如輸入字串abc,則打印出由字元a,b,c所能排列出來的所有字串abc,acb,bac,bca,cab和cba。 輸入描述 輸入一個字串,長度不超過9(可能有字元重複),字元只包括大小寫字母。 這道題是劍指offfer中一道
【資料結構與演算法】貪心演算法解決揹包問題。java程式碼實現
揹包問題(貪心演算法) 貪心演算法思想 簡單的說,就是將大問題轉化為最優子問題,例如本題所要求的,揹包容量有限,要想使物品的總價值最高,那麼,我們必須儘可能的選擇權重高的(即單位價值更高)的物品進行裝載。 在揹包問題中,物品是可拆的,即可以分成任意部分進行裝載,而最終實現的目標是
【演算法基礎】動態規劃的理解
本章是個很有趣的問題,也是難倒很多人的問題,同時這又是個會而不難的問題。 動態規劃的核心邏輯是:將問題分解為子問題。在《演算法圖解》這本書裡,深入淺出得講了遞推公式的推演邏輯,但是在關鍵部分,遞推公式部分,並沒給出邏輯。 整個過程好像是,前面一段道路很平緩,走起來很舒適,但是突然
【機器學習】EM演算法在高斯混合模型學習中的應用
前言 EM演算法,此部落格介紹了EMEM演算法相關理論知識,看本篇部落格前先熟悉EMEM演算法。 本篇部落格打算先從單個高斯分佈說起,然後推廣到多個高斯混合起來,最後給出高斯混合模型引數求解過程。 單個高斯分佈 假如我們有一些資料,這些資料來自同一個
【JAVA面試】JAVA常考點之資料結構與演算法(1)
JAVA常考點之資料結構與演算法(1) JAVA常考點之資料結構與演算法 目錄
【資料結構】平衡搜尋樹之---B樹的演算法實現
#include<iostream> using namespace std; #ifndef __BTREE_H__ #define __BTREE_H__ template<class K,int M=3>//設為三階B樹(每個陣列三個關鍵字
【演算法】----貪心演算法(揹包問題)
【前言:】 上一篇部落格從概念上說了一下貪心演算法,這次我們通過一個例項,來進一步幫助大家理解貪心演算法。 一、【經典例項:】(揹包問題) 給定n個物品和一個容量為C的揹包,物品i的重量是Wi,其價值為Vi,揹包問題是如何選擇入揹包的物品
【一週演算法實踐】__2.模型構建之整合模型
模型構建之整合模型 構建RF GBDT XDBoost LightGBM這四個模型,並對每一個模型使用準確率和AUC評分。在上次任務中使用了LR SVM DecisionTree這三個簡單的模型對樣本進行了預測和評價,請參照https://blog.csdn.net/wxq_1993/a
【演算法基礎】歸併排序的分析
歸併排序也是基本的排序之一,也挺重要的,所以寫這麼一篇部落格總結一下歸併排序的一個特性是,它對N個元素的檔案排序所需要的時間與NlogN成正比,它的缺點是所需需要的空間和N成正比,要克服這個缺點的話,會造成程式碼非常複雜而且開銷巨大。所以如果速度不是主要的問題,而且有足夠的空
老人餓了 【杭電-HDOJ-2187】 貪心演算法
Input 輸入資料首先包含一個正整數C,表示有C組測試用例,每組測試用例的第一行是兩個整數n和m(0<n<=1000,0<m<=1000),分別表示經費的金額和大米的種類,
【演算法基礎】DFS湊數
從給定的陣列arr中選取一個或多個數能否湊出給定的k? 原始碼 package com.javakk.ex; import java.util.Scanner; /** * @Ti
【C】貪心演算法
所謂的“貪心演算法”,就是每一次面臨選擇時,選擇最優、最先、最X的一項,反正就是突出一個“最”字。比如有1,4,3,2,讓你選兩個數,令選出來的數最大,你肯定按照每次選擇都選擇,剩餘數中最大的一個數,第一次選擇4,剩下還有1,3,2,這時傻子都會選擇3啊,從而得出在1,4,
0021演算法筆記——【貪心演算法】貪心演算法與活動安排問題
1、貪心演算法 (1)原理:在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,他所做出的僅是在某種意義上的區域性最優解。貪心演算法不是對所有問題都能得到整體最優解,但對範圍相當廣泛的許多問題他能產生整體最優解或者是整體最優解的近似解。