1. 哈夫曼編碼的原理是什麼
霍夫曼(Huffman)編碼屬於碼詞長度可變的編碼類,是霍夫曼在1952年提出的一種編碼方法,即從下到上的編碼方法。同其他碼詞長度可變的編碼一樣,可區別的不同碼詞的生成是基於不同符號出現的不同概率。
2. 霍夫曼編碼
霍夫曼(Huffman)在1952年提出
是一種從下到上的編碼方法,即從葉子逐步往上生成編碼樹
編碼演算法實際上是一個構造霍夫曼樹的過程(根據資料出現頻率的多寡來建造的樹,霍夫曼樹的樹葉節點用以儲存資料元素 ( Data Element ) ,若該元素出現的頻率越高,則由該元素至樹根所經過的節點數越少)
(1) 對資料中出現過的每一元素各自產生一外部節點,並賦予外部節點該元素之出現頻率。
(2) 令 L 是所有外部節點所成之集合。
(3) 產生一個新節點 N 。令 N 為 L1 和 L2 的父節點,L1 和 L2 是 L 中出現頻率最低的兩個節點。令 N 節點的出現頻率等於 L1 和 L2 的出現頻率總和。由 L 中刪除 L1 和 L2 ,並將 N 加入 L 中。
(4) 重復步驟 (3) 的動作,直到 | L | = 1 。
(5) 標示樹中各節點的左子樹鏈結為 0 ,右子樹鏈結為 1 。(不一定,只要一枝為0一枝為1)
是碼長可變的編碼
霍夫曼演算法和香農范諾演算法的編碼都不需要額外的同步碼(解釋)
霍夫曼樹是最小二叉樹,編碼效率比香農范諾高
霍夫曼編碼對錯誤敏感,錯一位,可能導致後面的解碼都是錯誤的,而且計算機也無法糾錯,我們稱為錯誤傳播
霍夫曼編碼是變長編碼,整個編碼結果是一個整體,無法隨意解壓縮其中的某一個部分
3. 赫夫曼樹
註:第一題 huffman 樹以及 huffman編碼
我將第二題與第三題與用鄰接矩陣存儲圖相關的操作放在了一起完成
第三題則是利用鄰接表
1.第一題
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<malloc.h>
#define LEN 8
#define MAXLEAF 6 // 最大葉子結點數目
#define MAXNODE (MAXLEAF*2)-1
typedef float ElemType;
typedef struct /* this structure stores the information of code */
{
int start; /* 存放編碼的起始位置右至左(高位至低位)*/
int bit[LEN]; /* 存放 huffman編碼 */
}HCode;
typedef HCode HuffCode[MAXLEAF];
typedef struct /* huffman tree結點的結構 */
{
int parent;
int LChild;
int RChild;
ElemType weight;
}HNode;
typedef HNode Huffman[MAXLEAF*2-1];
void createHuffmanTree(Huffman h,int leaves,ElemType *weight)
{
int i,j;
for(i=0;i<leaves*2-1;i++) /* 初始化huffman tree */
{
(h+i)->parent=-1;
(h+i)->LChild=-1;
(h+i)->RChild=-1;
(h+i)->weight=0;
}
for(i=0;i<leaves;i++) /* 給葉子賦權重 */
{
(h+i)->weight=*(weight+i);
}
/* 上一個循環葉子已經帶權,下面這個循環用來生成新根
* 新根數量為n-1
*/
for(i=0;i<leaves-1;i++)
{
ElemType m1, m2;
int m1_pos, m2_pos;
m1=m2=65536; /* m1存放最小的權值m2存放次小的權值 */
m1_pos=m2_pos=0; /* m1存放最小的權值對應下標m2存放次小的權值對應下標*/
for(j=0;j<leaves+i;j++)
{
if((h+j)->weight<m1&&(h+j)->parent==-1)
{
m2=m1;
m1=(h+j)->weight;
m2_pos=m1_pos;
m1_pos=j;
}
else if((h+j)->weight<m2&&(h+j)->parent==-1)
{
m2=(h+j)->weight;
m2_pos=j;
}
}
(h+leaves+i)->parent=-1; // 生成新根,無雙親-1
(h+leaves+i)->LChild=m1_pos; // 新根左孩子在數組中的下標
(h+leaves+i)->RChild=m2_pos; // 新根右孩子在數組中的下標
(h+m1_pos)->parent=leaves+i; // 原根的父親位置
(h+m2_pos)->parent=leaves+i; // 原根的父親位置
(h+leaves+i)->weight=m2+m1;
}
}
void huffmancode(Huffman h,HuffCode code,int leaves)
{
int i,j,p,c;
HCode hf;
/*從葉子結點開始向上回溯 從而計算出huffman code */
for(i=0;i<leaves;i++)
{
c=i;
p=h[i].parent;
hf.start=LEN-1;
while(p!=-1)
{
if(h[p].LChild==c)
{
hf.bit[hf.start]=0;
}
else
{
hf.bit[hf.start]=1;
}
--hf.start;
c=p;
p=h[c].parent;
}
for(j=hf.start+1;j<LEN;j++)
{
code[i].bit[j]=hf.bit[j];
}
code[i].start=hf.start+1;
}
}
void printhuffmantree(Huffman h,int leaves)
{
int i;
for(i=0;i<leaves*2-1;i++)
{
printf("weight=%-3.2f",h[i].weight);
printf("parent=%-3d",h[i].parent);
printf("LChild=%-3d",h[i].LChild);
printf("RChild=%-3d\n",h[i].RChild);
}
}
void printhuffcode(HuffCode hcode,char characters[])
{
int i,j;
for(i=0;i<strlen(characters);i++)
{
printf("%c的huffman編碼:",characters[i]);
for(j=hcode[i].start;j<LEN;j++)
{
printf("%d",hcode[i].bit[j]);
}
printf("\n");
}
}
int main(void)
{
Huffman h;
HuffCode hcode;
char characters[]={"abcdef"}; /* 待編碼的字元 */
ElemType weights[]={0.3,0.7,0.4,0.5,0.9,0.1}; /* 字元出現的頻率 */
createHuffmanTree(h,strlen(characters),weights);
printhuffmantree(h,strlen(characters));
huffmancode(h,hcode,sizeof(characters));
printhuffcode(hcode,characters);
system("pause");
return 0;
}
2.第二題 代碼如下,以及使用說明
例如有如下的圖
a->b
/ \
|
c
首先輸入頂點與弧的數目
3 2
提示輸入頂點
abc
輸入弧(弧頭弧尾)
ab
ca
那些插入以及刪除的操作已經放入主函數
顧回車後可以看到進行相關操作後圖的變化
#include<stdio.h>
#include<stdlib.h>
#define MAXVERT 10 // 需要在圖中進行插入操作所以設定一個最大值
#define INFINITE 32767
#define ERROR -1
#define FALSE 0
#define OK 1
typedef int ElemType;
enum maritype{DG,UDG,DN,UDN};
typedef struct
{
char vertex[MAXVERT];
ElemType arc[MAXVERT][MAXVERT];
int ArcNum;
int VertexNum;
}adjacentMatrix;
int locate(adjacentMatrix *G,char v)
{
int i, k=ERROR;
for(i=0;i<G->VertexNum;i++)
{
if(G->vertex[i]==v)
{
k=i;
break;
}
}
return(k);
}
void init(adjacentMatrix *G)
{
int i,j;
for(i=0;i<G->VertexNum;i++)
{
for(j=0;j<G->VertexNum;j++)
{
G->arc[i][j]=0;
}
}
}
void createDN(adjacentMatrix *G)
{
int i,j,k;
char v1,v2,blank;
printf("請輸入頂點與弧的數目 \n");
scanf("%d%d",&G->VertexNum,&G->ArcNum);
init(G);
printf("請輸入頂點(用字母表示):\n");
getchar();
for(i=0;i<G->VertexNum;i++)
{
scanf("%c",&G->vertex[i]);
}
getchar();
for(i=0;i<G->ArcNum;i++)
{
printf("請輸入弧%d的弧頭與弧尾",i+1);
scanf("%c%c",&v1,&v2);
getchar();
j=locate(G,v1);
k=locate(G,v2);
G->arc[j][k]=1;
}
}
int InsertVex(adjacentMatrix *G,char v) /* insert vertex */
{
int i;
if(G->VertexNum>MAXVERT-1)
{
return(FALSE);
}
G->vertex[G->VertexNum++]=v;
for(i=0;i<G->VertexNum;i++)
{
G->arc[i][G->VertexNum-1]=G->arc[G->VertexNum-1][i]=0;
}
return(OK);
}
int InsertAar(adjacentMatrix *G,char v,char w) //插入邊
{
int i,j;
i=locate(G,v);
j=locate(G,w);
if(i==-1||j==-1)
{
return(FALSE);
}
G->arc[i][j]=1;
return(OK);
}
int DeleteVex(adjacentMatrix *G,char v) //(刪除頂點)
{
int i, k;
k=locate(G,v);
if(k==-1)
{
printf("The vertex has not found\n");
return(FALSE);
}
for(i=k;i<G->VertexNum;i++)
{
G->vertex[i]=G->vertex[i+1];
}
--G->VertexNum;
return(OK);
}
int DeleteArc(adjacentMatrix *G,char v,char w)
{
int i,j;
i=locate(G,v);
j=locate(G,w);
if(i==-1||j==-1)
{
return(ERROR);
}
G->arc[i][j]=0;
return(OK);
}
void degree(adjacentMatrix *G)
{
int i, j, odsum, idsum, zero=0;
for(i=0;i<G->VertexNum;i++)
{
odsum=0;
idsum=0;
for(j=0;j<G->VertexNum;j++)
{
odsum+=G->arc[i][j];
idsum+=G->arc[j][i];
}
if(!odsum)
{
++zero;
}
printf("頂點 %c 的出度與入度是",G->vertex[i]);
printf("%3d%3d\n",odsum,idsum);
}
printf("度為0的頂點 %d\n",zero);
}
void print(adjacentMatrix *G)
{
int i,j;
for(i=0;i<G->VertexNum;i++)
{
printf("%8c",G->vertex[i]);
}
printf("\n");
for(i=0;i<G->VertexNum;i++)
{
for(j=0;j<G->VertexNum;j++)
{
if(!j)
{
printf("%c",G->vertex[i]);
}
printf("%8d",G->arc[i][j]);
}
printf("\n");
}
}
int main(void)
{
int k;
char v, w;
adjacentMatrix G;
createDN(&G);
print(&G); // 鄰接矩陣列印
degree(&G); // 求所有頂點出度入度 及度為0的點
InsertVex(&G,'f'); // 插入頂點f
InsertAar(&G,'f','c'); // 插入邊 fc
degree(&G); // 觀察插入邊頂點後度的變化
print(&G); // 鄰接矩陣列印
DeleteArc(&G,'f','c'); // 刪除邊 fc
print(&G); // 鄰接矩陣列印 觀察變化
DeleteVex(&G,'a'); // 刪除頂點a
print(&G); // 鄰接矩陣列印 觀察刪除頂點a後變化
system("pause");
return(0);
}
3.使用同上 示例圖也如上所畫 使用方式也基本一直
按你的要求只輸出 頂點的出度入度以及度為0的頂點
#include<stdio.h>
#include<stdlib.h>
#define MAX_VERTEX_NUM 10
#define ERROR -1
#define FALSE 0
#define OK 1
typedef char VertexType;
typedef struct ArcNode // 邊表的結構
{
int adjvex; // 與頂點相鄰接的頂點在表頭結點表(圖中)的位置
struct ArcNode *nextarc;
}ArcNode;
typedef struct VertexNode // 表頭結點表的結構
{
VertexType data;
ArcNode *firstarc;
}VertexNode;
typedef struct // 存放圖信息的結構
{
int vexnum, arcnum; // 頂點與弧的數目
VertexNode vertex[MAX_VERTEX_NUM];
}Adjlist;
int locate(Adjlist *G,char v)
{
int i, k=ERROR;
for(i=0;i<G->vexnum;i++)
{
if(G->vertex[i].data==v)
{
k=i;
break;
}
}
return(k);
}
void createDG(Adjlist *G)
{
int i, j, k;
char v1, v2;
ArcNode *s;
printf("輸入頂點與弧的數目 \n");
scanf("%d%d",&G->vexnum,&G->arcnum);
getchar();
printf("請輸入頂點(用字母表示):");
for(i=0;i<G->vexnum;i++) // 生成表頭結點表
{
scanf("%c",&G->vertex[i].data);
G->vertex[i].firstarc=NULL;
}
getchar();
for(i=0;i<G->arcnum;i++)
{
printf("請輸入第%d條邊的信息 弧尾與弧頭:",i+1);
scanf("%c%c",&v1,&v2);
getchar();
j=locate(G,v1);
k=locate(G,v2);
s=(ArcNode *)malloc(sizeof(ArcNode));
s->adjvex=k;
s->nextarc=G->vertex[j].firstarc;
G->vertex[j].firstarc=s;
}
}
void od(Adjlist *G)
{
int odsum, i;
ArcNode *p;
for(i=0;i<G->vexnum;i++) // 生成表頭結點表
{
odsum=0;
p=G->vertex[i].firstarc;
while(p)
{
++odsum;
p=p->nextarc;
}
printf("\n%c的出度是:%d\n ",G->vertex[i].data,odsum);
}
}
void ind(Adjlist *G)
{
int insum, i, j, k;
ArcNode *p;
for(i=0;i<G->vexnum;i++) // 生成表頭結點表
{
insum=0;
for(j=0;j<G->vexnum;j++)
{
if(i==j)
{
continue;
}
p=G->vertex[j].firstarc;
while(p)
{
if(p->adjvex==i)
{
++insum;
}
p=p->nextarc;
}
}
printf("\n%c的入度是:%d\n ",G->vertex[i].data,insum);
}
}
int main(void)
{
Adjlist G;
int i;
createDG(&G);
od(&G);
ind(&G);
system("pause");
return(0);
}
4. 怎樣將建立好的哈夫曼樹保存在文件中
哈夫曼編碼是哈夫曼樹的一個應用。哈夫曼編碼應用廣泛,如JPEG中就應用了哈夫曼編碼。
首先介紹什麼是哈夫曼樹。哈夫曼樹又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹。所謂樹的帶權路徑長度,就是樹中所有的葉結點的權值乘上其到根結點的路徑長度(若根結點為0層,葉結點到根結點的路徑長度為葉結點的層數)。樹的帶權路徑長度記為 WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln),N個權值Wi(i=1,2,...n)構成一棵有N個葉結點的二叉樹,相應的葉結點的路徑長度為Li(i=1,2,...n)。可以證明哈夫曼樹的WPL是最小的。
哈夫曼在上世紀五十年代初就提出這種編碼時,根據字元出現的概率來構造平均長度最短的編碼。它是一種變長的編碼。在編碼中,若各碼字長度嚴格按照碼字所對應符號出現概率的大小的逆序排列,則編碼的平均長度是最小的。(註:碼字即為符號經哈夫曼編碼後得到的編碼,其長度是因符號出現的概率而不同,所以說哈夫曼編碼是變長的編碼。)
一、對給定的n個權值{W1,W2,W3,...,Wi,...,Wn}構成n棵二叉樹的初始集合F={T1,T2,T3,...,Ti,...,Tn},其中每棵二叉樹Ti中只有一個權值為Wi的根結點,它的左右子樹均為空。(為方便在計算機上實現演算法,一般還要求以Ti的權值Wi的升序排列。)
二、在F中選取兩棵根結點權值最小的樹作為新構造的二叉樹的左右子樹,新二叉樹的根結點的權值為其左右子樹的根結點的權值之和。
三、從F中刪除這兩棵樹,並把這棵新的二叉樹同樣以升序排列加入到集合F中。
四、重復二和三兩步,直到集合F中只有一棵二叉樹為止。
用C語言實現上述演算法,可用靜態的二叉樹或動態的二叉樹。若用動態的二叉樹可用以下數據結構: struct tree{
float weight; /*權值*/union{char leaf; /*葉結點信息字元*/
struct tree *left; /*樹的左結點*/};struct tree *right; /*樹的右結點*/};struct forest{ /*F集合,以鏈表形式表示*/
struct tree *ti; /* F中的樹*/
struct forest *next; /* 下一個結點*/};例:若字母A,B,Z,C出現的概率為:0.75,0.54,0.28,0.43;則相應的權值為:75,54,28,43。
構造好哈夫曼樹後,就可根據哈夫曼樹進行編碼。例如:上面的字元根據其出現的概率作為權值構造一棵哈夫曼樹後,經哈夫曼編碼得到的對應的碼值。只要使用同一棵哈夫曼樹,就可把編碼還原成原來那組字元。顯然哈夫曼編碼是前綴編碼,即任一個字元的編碼都不是另一個字元的編碼的前綴,否則,編碼就不能進行翻譯。例如:a,b,c,d的編碼為:0,10,101,11,對於編碼串:1010就可翻譯為bb 或ca,因為b的編碼是c的編碼的前綴。剛才進行哈夫曼編碼的規則是從根結點到葉結點(包含原信息)的路徑,向左孩子前進編碼為0,向右孩子前進編碼為 1,當然你也可以反過來規定。
這種編碼方法是靜態的哈夫曼編碼,它對需要編碼的數據進行兩遍掃描:第一遍統計原數據中各字元出現的頻率,利用得到的頻率值創建哈夫曼樹,並必須把樹的信息保存起來,即把字元 0-255(2^8=256)的頻率值以2-4BYTES的長度順序存儲起來,(用4Bytes的長度存儲頻率值,頻率值的表示範圍為 0--2^32-1,這已足夠表示大文件中字元出現的頻率了)以便解壓時創建同樣的哈夫曼樹進行解壓;第二遍則根據第一遍掃描得到的哈夫曼樹進行編碼,並把編碼後得到的碼字存儲起來。靜態哈夫曼編碼方法有一些缺點:一、對於過短的文件進行編碼的意義不大,因為光以4BYTES的長度存儲哈夫曼樹的信息就需1024Bytes的存儲空間;二、進行哈夫曼編碼,存儲編碼信息時,若用與通訊網路,就會引起較大的延時;三、對較大的文件進行編碼時,頻繁的磁碟讀寫訪問會降低數據編碼的速度。
因此,後來有人提出了一種動態的哈夫曼編碼方法。動態哈夫曼編碼使用一棵動態變化的哈夫曼樹,對第t+1個字元的編碼是根據原始數據中前t個字元得到的哈夫曼樹來進行的,編碼和解碼使用相同的初始哈夫曼樹,每處理完一個字元,編碼和解碼使用相同的方法修改哈夫曼樹,所以沒有必要為解碼而保存哈夫曼樹的信息。編碼和解碼一個字元所需的時間與該字元的編碼長度成正比,所以動態哈夫曼編碼可實時進行。動態哈夫曼編碼比靜態哈夫曼編碼復雜的多,有興趣的讀者可參考有關數據結構與演算法的書籍。
前面提到的JPEG中用到了哈夫曼編碼,並不是說JPEG就只用哈夫曼編碼就可以了,而是一幅圖片經過多個步驟後得到它的一列數值,對這些數值進行哈夫曼編碼,以便存儲或傳輸。哈夫曼編碼方法比較易懂,大家可以根據它的編碼方法,自己編寫哈夫曼編碼和解碼的程序。
5. 哈夫曼樹及哈夫曼編碼的C程序實現(數據結構題)
去年做的課程設計,有什麼不合要求的自己改改
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
int m,s1,s2;
typedef struct {
unsigned int weight;
unsigned int parent,lchild,rchild;
}HTNode,*HuffmanTree; //動態分配數組存儲哈夫曼樹
typedef char *HuffmanCode; //動態分配數組存儲哈夫曼編碼表
void Select(HuffmanTree HT,int n) {
int i,j;
for(i = 1;i <= n;i++)
if(!HT[i].parent){s1 = i;break;}
for(j = i+1;j <= n;j++)
if(!HT[j].parent){s2 = j;break;}
for(i = 1;i <= n;i++)
if((HT[s1].weight>HT[i].weight)&&(!HT[i].parent)&&(s2!=i))s1=i;
for(j = 1;j <= n;j++)
if((HT[s2].weight>HT[j].weight)&&(!HT[j].parent)&&(s1!=j))s2=j;
}
void HuffmanCoding(HuffmanTree &HT, HuffmanCode HC[], int *w, int n) {
// 演算法6.13
// w存放n個字元的權值(均>0),構造哈夫曼樹HT,
// 並求出n個字元的哈夫曼編碼HC
int i, j;
char *cd;
int p;
int cdlen;
if (n<=1) return;
m = 2 * n - 1;
HT = (HuffmanTree)malloc((m+1) * sizeof(HTNode)); // 0號單元未用
for (i=1; i<=n; i++) { //初始化
HT[i].weight=w[i-1];
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
for (i=n+1; i<=m; i++) { //初始化
HT[i].weight=0;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
puts("\n哈夫曼樹的構造過程如下所示:");
printf("HT初態:\n 結點 weight parent lchild rchild");
for (i=1; i<=m; i++)
printf("\n%4d%8d%8d%8d%8d",i,HT[i].weight,
HT[i].parent,HT[i].lchild, HT[i].rchild);
printf(" 按任意鍵,繼續 ...");
getchar();
for (i=n+1; i<=m; i++) { // 建哈夫曼樹
// 在HT[1..i-1]中選擇parent為0且weight最小的兩個結點,
// 其序號分別為s1和s2。
Select(HT, i-1);
HT[s1].parent = i; HT[s2].parent = i;
HT[i].lchild = s1; HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
printf("\nselect: s1=%d s2=%d\n", s1, s2);
printf(" 結點 weight parent lchild rchild");
for (j=1; j<=i; j++)
printf("\n%4d%8d%8d%8d%8d",j,HT[j].weight,
HT[j].parent,HT[j].lchild, HT[j].rchild);
printf(" 按任意鍵,繼續 ...");
getchar();
}
//------無棧非遞歸遍歷哈夫曼樹,求哈夫曼編碼
cd = (char *)malloc(n*sizeof(char)); // 分配求編碼的工作空間
p = m; cdlen = 0;
for (i=1; i<=m; ++i) // 遍歷哈夫曼樹時用作結點狀態標志
HT[i].weight = 0;
while (p) {
if (HT[p].weight==0) { // 向左
HT[p].weight = 1;
if (HT[p].lchild != 0) { p = HT[p].lchild; cd[cdlen++] ='0'; }
else if (HT[p].rchild == 0) { // 登記葉子結點的字元的編碼
HC[p] = (char *)malloc((cdlen+1) * sizeof(char));
cd[cdlen] ='\0'; strcpy(HC[p], cd); // 復制編碼(串)
}
} else if (HT[p].weight==1) { // 向右
HT[p].weight = 2;
if (HT[p].rchild != 0) { p = HT[p].rchild; cd[cdlen++] ='1'; }
} else { // HT[p].weight==2,退回退到父結點,編碼長度減1
HT[p].weight = 0; p = HT[p].parent; --cdlen;
}
}
} // HuffmanCoding
void main() {
HuffmanTree HT;HuffmanCode *HC;int *w,n,i;
puts("輸入結點數:");
scanf("%d",&n);
HC = (HuffmanCode *)malloc(n*sizeof(HuffmanCode));
w = (int *)malloc(n*sizeof(int));
printf("輸入%d個結點的權值\n",n);
for(i = 0;i < n;i++)
scanf("%d",&w[i]);
HuffmanCoding(HT,HC,w,n);
puts("\n各結點的哈夫曼編碼:");
for(i = 1;i <= n;i++)
printf("%2d(%4d):%s\n",i,w[i-1],HC[i]);
getchar();
}
6. 什麼是哈夫曼樹呢
夫曼樹是帶權路徑長度最小的二叉樹,用途是平均查找信息的代價最小。
普通二叉樹的用途也普通,比較通用,就是信息存儲和查找。
普通二叉樹可能有的只有一個子節點,而哈夫曼樹一定有兩個。