1. 程式人生 > >資料結構之哈夫曼樹

資料結構之哈夫曼樹

一.什麼是哈夫曼樹

基本概念
    節點之間的路徑:一個結點到另一個結點,所經過節點的結點序列。
    結點之間的路徑長度:結點之間路徑上的分支數(邊),如汽車到下一站的路徑長度為1。
    樹的路徑長度:從根結點到每個葉子結點的路徑長度之和。
    帶權路徑: 路徑上加上的實際意義 。如汽車到下一站的距離我們叫做權值 
    樹的帶權路經長度: 每個葉子結點到根的路徑長度*權值 之和,記作WPL。還是汽車的例子,汽車到達天津有2條路 可以走。第一條路經過3個站,每個站相距13km。第二條有2個站,每個站相距18km。那麼有距離的路我們叫做帶權路徑。根結點為天津的樹,那麼第一條路帶權路徑為 3*13 = 39,第二條為2*18。樹的帶權路徑WPL 3*13+2*18.
    哈夫曼樹: 重點來了,什麼是哈夫曼樹呢。二叉樹是n個結點的結合,它的度(所有孩子個數的最大值)小於等於2。n個結點構成一個二叉樹有許多方法。使二叉樹的帶權路徑最小的樹 ,我們叫做哈夫曼樹。

二.哈夫曼樹有什麼用

介紹了這麼多概念,不知道它有什麼用,讓初學者感覺資料結構沒什麼勁。哈夫曼樹主要用在資料的壓縮如JPEG格式圖片,在通訊中我們可以先對傳送的資料進行哈夫曼編碼壓縮資料提高傳輸速度。查詢優化 在工作中我們我們身邊放許多工具,由於空間限制我們不能把所有工具放在我們最容易拿到的地方,所有我們把使用頻率最高的工具放在最容易的位置。同樣的道理在查詢的時候我們把查詢頻率最高的資料建立索引,這些都是使用了哈夫曼演算法的思想。 

三.怎麼構造哈夫曼樹

給定權值為12 3 6 8 2  如果構造其哈夫曼樹

1.給定n個權值{w1,w2,w3…..,wn}構成 n棵二叉樹的集合F={T1,T2,T3…..Tn}, 其中每棵樹只有權值為Wi的根節點。
構成的二叉樹為
這裡寫圖片描述


2.在F中選取權值最小的兩棵樹作為 左子樹和右子樹  構成新的二叉樹根的權值為兩棵樹之和
3.將前兩棵樹從F刪除 加入新樹
這裡寫圖片描述
4.重複2和3直到只剩一棵樹
這裡寫圖片描述

三.程式碼實現哈夫曼樹

程式功能輸入一串字元,統計字元個數,並把字元個數作為權值構造一棵哈夫曼樹,求出字元的編碼
程式效果圖
這裡寫圖片描述

1.程式碼


//標頭檔案 
//為了學習 我使用連結串列來實現 
//從樹種找出權值最小的兩個樹 我先對樹排序 取最小的兩棵 
//新的樹先和最後一棵樹 比較 找到合適位置進行插入 也就是插入排序
#include <stdio.h>
#define STACKSIZE 20        //棧的大小
typedef struct char_table { char ch; //統計的字元 int count; //字元出現的次數 struct char_table * next; //指向下一個字元表指標 }CharNode; typedef struct { int weight; //結點權值 int parent; //節點指向父節點的指標 char ch; //葉子節點的字元 int lchirld, rchirld; //指向結點的左右孩子 }HTNode,*HuffmanTree; typedef struct stack{ int data[STACKSIZE]; //給棧分配記憶體 int top; //棧頂 }Stack1; void InitStack1(Stack1*); //棧的初始化 int IsEmptyStack1(Stack1*); //判斷棧是否為空 void Push1(int, Stack1*s); //入棧操作 int Pull1(Stack1*s); //出棧操作 CharNode* CountChar(char *); //統計字元個數 HuffmanTree HuffManEncodeing(CharNode* table); //進行哈夫曼編碼 void testHafuman(); //測試函式 /*-------------------------------------------------------------------*/ //標頭檔案的實現 d Push1(int t, Stack1*s){ if (s->top == STACKSIZE - 1) { printf("棧滿\n"); return; } s->top++; s->data[s->top] = t; } //出棧 int Pull1(Stack1*s) { int p = s->data[s->top]; s->top--; return p; } //統計字串中出現的字元和個數 CharNode* CountChar(char *c) { CharNode * head=NULL; CharNode * p=NULL,*last=NULL; int isFind=0; //迴圈查詢所有字元 while (*c != '\0') { //連結串列中為空 則建立連結串列 if (head == NULL) { head = (CharNode*)malloc(sizeof(CharNode)); head->ch = *c; head->count=1; head->next = NULL; } //如果連結串列不為空且連結串列已經建立該字元節點 則字元數增加 否則建立新節點 else { isFind = 0; p = head; //查詢是否有該字元節點 while (p != NULL) { //查詢到字元 字元數+1 結束本次迴圈 if (p->ch == *c) { p->count++; isFind = 1; break; } last = p; //最後一個節點 p = p->next; } //在連結串列中沒有字元 建立字元節點 if (isFind == 0) { CharNode* n = (CharNode*)malloc(sizeof(CharNode)); n->ch = *c; n->count=1; n->next = NULL; last->next=n; } } //字元指標指向下個字元 c++; } return head; } //清除字元連結串列的所有記憶體 void ClearCharTable(CharNode* table) { while(table!= NULL) { CharNode * p = table->next; free(table); table = p; } } //列印字元及統計資訊 void DispCharTable(CharNode * table) { while (table != NULL) { printf("%c:%d\n", table->ch, table->count); table = table->next; } } //對連結串列進行排序 CharNode* SortCharTable(CharNode * t) { //中轉指標 CharNode* p = t; //頭指標 CharNode*head = t; //當前下一個指標 CharNode * next = NULL; //當前指標 CharNode * current = NULL; //當前指標的下下個指標 CharNode * afterNext = NULL; //先前一個指標 CharNode * previous = NULL; int count = 0,i,j; while (p != NULL) { count++; p = p->next; } for (i = 0; i < count-1; i++) { current = head; next = head->next; afterNext = next->next; previous = NULL; for (j = 0; j < count - 1 - i; j++) { if ((current->count > next->count)&&j==0) { head = next; next->next = current; current->next = afterNext; //移動到下個節點 previous = next; current = previous->next; next = current->next; if (afterNext == NULL) { } else { afterNext = afterNext->next; } } else if (current->count > next->count) { //交換節點 previous->next = next; next->next = current; current->next = afterNext; //移動到下個節點 previous = next; current = previous->next; next = current->next; if (afterNext == NULL) { } else { afterNext = afterNext->next; } } else { previous = current; current = next; next = afterNext; if (afterNext == NULL) { } else { afterNext = afterNext->next; } } } } return head; } //獲取連結串列的長度 int GetLength(CharNode*t) { int len = 0; while (t != NULL) { len++; t = t->next; } return len; } //獲取第i個元素 CharNode * Get(CharNode* t, int i) { int len = 1; while (t != NULL) { if (len == i) { return t; } len++; t = t->next; } return NULL; } //哈夫曼樹的構造 HuffmanTree HuffManEncodeing(CharNode* table) { int i, j; int len = GetLength(table); //編碼字元個數 HuffmanTree tree = (HuffmanTree)malloc(2 * len*sizeof(HTNode)); //編碼n個字元 需要 2n-1 個節點 多分配一個給排序用 //給定每個權值 為每個權值建立一根樹 for (i = 1; i <= len ; i++) { CharNode * n = Get(table, i); tree[i].weight = n->count; tree[i].lchirld = 0; tree[i].parent = 0; tree[i].rchirld = 0; tree[i].ch = n->ch; } //初始化後面的樹 for (i = len + 1; i <2 * len; i++) { tree[i].weight =0; tree[i].lchirld = 0; tree[i].parent = 0; tree[i].rchirld = 0; tree[i].ch = '\0'; } //找出最小的兩個樹構成一棵新二叉樹 其權值是左後孩子的權值和 for (i = 1; i < 2*len -1 ; i+=2) { //找出權值最小的兩個樹 int weight = tree[i].weight + tree[i + 1].weight; tree[i].parent = 0; tree[i + 1].parent = 0; //將合成的新節點儲存在 0位置 tree[0].weight = weight; tree[0].lchirld = i; tree[0].rchirld = i+1; //將新節點的插入到合適的位置 for (j = len+(i/2); j < 1; j--) { if (tree[j].weight<tree[0].weight) { break; } tree[j + 1] = tree[j]; } if (j == len + (i / 2)) { tree[tree[0].lchirld].parent = j + 1; tree[tree[0].rchirld].parent = j + 1; tree[j+1] = tree[0]; } else { tree[tree[0].lchirld].parent = j ; tree[tree[0].rchirld].parent = j ; tree[j] = tree[0]; } } tree[2 * len - 1].parent = 0; //初始化棧 Stack1 s; InitStack1(&s); //從霍夫曼樹中隊字元編碼 for (i = 1; i <= len; i++) { //查詢到根的路徑 printf("\n%c %d ", tree[i].ch,tree[i].weight); Push1(i, &s); int parent = tree[i].parent,child; while (parent != 0) { Push1(parent, &s); parent = tree[parent].parent; } parent = Pull1(&s); while (!IsEmptyStack1(&s)){ child = Pull1(&s); if (child == tree[parent].lchirld) { printf("0"); } else { printf("1"); } parent = child; } } printf("\n"); return tree; } //清除哈夫曼樹的記憶體 void ClearHuffman(HuffmanTree tree) { free(tree); } //列印哈夫曼樹 void DispHuffman(HuffmanTree tree,int n) { int i = 0; for (i = 1; i < 2 * n;i++) { printf("i:%d w:%d c:%c l:%d r:%d p:%d\n", i, tree[i].weight, tree[i].ch, tree[i].lchirld, tree[i].rchirld, tree[i].parent); } } //測試函式 void testHafuman(){ printf("請輸入編碼字串:"); char buf[1024]; scanf("%s", buf); CharNode * p = CountChar(buf); CharNode * a = SortCharTable(p); DispCharTable(a); HuffmanTree tree = HuffManEncodeing(a); ClearCharTable(a); ClearHuffman(tree); }