C語言實現哈夫曼編碼(最小堆,二叉樹)


// 文件中有通過QT實現的界面
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct HNode *Heap; /* 堆的類型定義 */ typedef struct SData myData; typedef struct SData *HuffmanTree; typedef struct Ans SAns; struct Ans // 存儲最終結果 { char ch; // 表示字符 char *s; // 一個字符串, 表示結點的哈夫曼編碼 }; struct SData // 哈夫曼樹的結構 { int freq; // 結點元素出現的頻率 char ch; // 結點元素的字符 HuffmanTree Left, Right; // 此哈夫曼結點的左子樹和右子樹 }; int i = 0; // 充當遍歷哈夫曼樹時對結果結構體數組ans賦值的下標 struct HNode // 堆的結構體 { myData *Data; // 存儲元素的數組 int Size; // 堆中當前元素個數 int Capacity; // 堆的最大容量 }; typedef Heap MinHeap; // 最小堆 MinHeap CreateHeap(int MaxSize); // 創建最小堆 int IsFull(MinHeap H); // 判斷最小堆是否已滿 int Insert(MinHeap H, myData X); // 往最小堆中插入元素 int IsEmpty(MinHeap H); // 判斷最小堆是否為空 myData DeleteMin(MinHeap H); // 刪除最小堆頂的元素, 即最小元素(自定義'小') char* MatchingString(char *s1, char *s2); // 連接不定長的字符串s1和s2, 返回新字符串 char* TraversalHT(myData *d, char *s, SAns *SAnsP); // 遞歸遍歷哈弗曼樹, 直到收錄所有葉節點的數據 void InsertSort(SAns *ans, int N); // 插入排序 char* binarySearch(SAns *ans, char ch, int end); // 迭代實現的二分查找 int main() { MinHeap heap = CreateHeap(100); char ch; int freq; printf("先輸入數字代表權值(大於0),再輸入對應的字符,按Ctrl+Z結束\n"); while (scanf("%d %c", &freq, &ch) != EOF) { myData temp = { freq, ch, NULL, NULL }; Insert(heap, temp); // 將輸入的數據放在結構體中, 插入到最小堆里 } HuffmanTree T; const int size = heap->Size; // 定義const常量size為初始時刻堆的大小 SAns *ans = (SAns*)malloc(sizeof(SAns)*size); // 通過連續兩次取最小堆的堆頂元素, 且先取出的存放在左子樹, 后取出的存放在右子樹 // 合成一顆二叉樹, 再將其插入最小堆中 // 反復進行此操作size-1次, 最終堆頂的元素就是我們所求的哈弗曼樹 // 這里說的堆頂並不是指heap->Data[0], 因為heap->Data[0]已用於放置哨兵 for (int j = 1; j < size; j++) { T = (HuffmanTree)malloc(sizeof(myData)); T->Left = (HuffmanTree)malloc(sizeof(myData)); T->Right = (HuffmanTree)malloc(sizeof(myData)); *T->Left = (DeleteMin(heap)); *T->Right = (DeleteMin(heap)); T->freq = T->Left->freq + T->Right->freq; Insert(heap, *T); // printf("%d\n", heap->Data[1].freq); // 輸出總頻率, 也可以說是權重, 檢驗結果是否正確 } TraversalHT(&heap->Data[1], "", ans); // 遍歷哈弗曼樹, 將結果放在ans結構體數組中 // for (int k = 0; k < size; k++) // 檢驗 // { // printf("%c %s\n", ans[k].ch, ans[k].s); // } // printf("**********************\n"); InsertSort(ans, size); // 對ans數組進行插入排序, 元素的大小取決於字符ch的大小 // for (int k = 0; k < size; k++) // 檢驗排序的結果 // { // printf("%c %s\n", ans[k].ch, ans[k].s); // } printf("輸入需要翻譯的字符串:"); char str[100]; // 開一個足夠大的字符數組, 用於存放需要翻譯的字符串 while (scanf("%s", str) != EOF) { for (int w = 0; w < strlen(str); w++) { // 遍歷str, 通過二分查找, 找到與字符對應的哈夫曼編碼 printf("%s", binarySearch(ans, str[w], size - 1)); } printf("\n"); printf("輸入需要翻譯的字符串:"); } return 0; } char* binarySearch(SAns *ans, char ch, int end) { // 經典的二分查找, 算法詳細過程省略... int mid; int beg = 0; while (end >= beg) { mid = (beg + end) / 2; if (ans[mid].ch > ch) beg = mid + 1; else if (ans[mid].ch < ch) end = mid - 1; else return ans[mid].s; } return NULL; // 消除warning } void InsertSort(SAns *ans, int N) { // 經典的插入排序, 算法詳細過程省略... for (int p = 1; p < N; p++) { SAns temp = ans[p]; int i; for (i = p; i > 0 && ((int)ans[i - 1].ch < (int)temp.ch); i--) ans[i] = ans[i - 1]; ans[i] = temp; } } char* MatchingString(char *s1, char *s2) { // 由於本程序采用C語言編寫, 沒有內置string類, 且需要連接不定長的字符串s1和s2 // 故先申請一塊動態內存, 用於存放結果t // 先將不定長的s1復制到t中, 之后直接通過strcat()函數把s2接在t后面,返回t char *t = (char*)malloc(strlen(s1) + strlen(s2) + 1); if (t == NULL) exit(1); strcpy(t, s1); strcat(t, s2); return t; } char* TraversalHT(myData *d, char *s, SAns *SAnsP) { // 接收參數 d, s, SAnsP, // d代表哈弗曼樹結構, s用於存放遍歷到此節點時的哈夫曼編碼, // SAnsP用於存放通過遍歷得到的葉節點的字符和哈夫曼編碼所構成的結果 if (d->Left) // 若d存在左子樹, 則繼續遍歷其左子樹, 並且在字符串s后面拼接上字符串"0" TraversalHT(d->Left, MatchingString(s, "0"), SAnsP); else { // 不存在左子樹, 則必定不存在右子樹, 此時只需保存結果, 接着就可以返回NULL結束這次遞歸 SAns temp = { d->ch, s }; SAnsP[i] = temp; printf("編碼: %c %s\n", d->ch, s); i++; // 結果數組下標+1 return NULL; } if (d->Right) // 若d存在右子樹, 則繼續遍歷其右子樹, 並且在字符串s后面拼接上字符串"1" TraversalHT(d->Right, MatchingString(s, "1"), SAnsP); return NULL; // 消除warning } MinHeap CreateHeap(int MaxSize) { // 創建容量為MaxSize的空的最小堆 MinHeap H = (MinHeap)malloc(sizeof(struct HNode)); H->Data = (myData *)malloc((MaxSize + 1) * sizeof(myData)); H->Size = 0; H->Capacity = MaxSize; H->Data[0].freq = -1; // 定義"哨兵"為小於堆中所有可能元素的值, 這里可以是-1 //有了哨兵就不必在后續的遍歷中的for循環判斷條件中加入(...&&i>1),可有效提高效率 return H; } int IsFull(MinHeap H) { return (H->Size == H->Capacity); } int Insert(MinHeap H, myData X) { // 將元素X插入最小堆H,其中H->Data[0]已經定義為哨兵 int i; if (IsFull(H)) { printf("最小堆已滿\n"); return 0; } i = ++H->Size; // i指向插入后堆中的最后一個元素的位置 for (; H->Data[i / 2].freq > X.freq; i /= 2) H->Data[i] = H->Data[i / 2]; // 上濾X,最終i就是X的下標 H->Data[i] = X; // 將X插入 return 1; } int IsEmpty(MinHeap H) { return (H->Size == 0); } myData DeleteMin(MinHeap H) { // 從最小堆H中取出鍵值為最小的元素,並刪除一個結點 int Parent, Child; myData MinItem, X; if (IsEmpty(H)) { printf("最小堆已為空\n"); X.freq = -1; return X; //-1表示刪除元素失敗 } MinItem = H->Data[1]; // 取出根結點存放的最小值 // 用最小堆中最后一個元素從根結點開始向上過濾下層結點 X = H->Data[H->Size--]; // 同時減小當前堆的規模 for (Parent = 1; Parent * 2 <= H->Size; Parent = Child) { Child = Parent * 2; if ((Child != H->Size) && (H->Data[Child].freq > H->Data[Child + 1].freq)) Child++; // Child指向左右子結點的較小者 if (X.freq <= H->Data[Child].freq) break; // 找到了合適位置 else // 下濾X H->Data[Parent] = H->Data[Child]; } H->Data[Parent] = X; return MinItem; }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM