// 文件中有通過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;
}