今日一言:
永遠不需要解釋你自己,
因為喜歡你的人不需要
不喜歡你的人不相信。
C語言實現 —— 哈夫曼編碼
我已經被它肝得無話可說,
這是第n次寫了。
代碼
/*********************************************************************************
*
* 哈夫曼編碼
* create: 2020年5月22日 16點42分
* author: LOS(小魚)
*
* *******************************************************************************
* 本程序所使用的數據結構:
* 1. 雙向鏈表
* 2. 二叉樹
* *******************************************************************************
* 提示:
* 同一條件下,所構造的哈夫曼樹可以不同,
* 本程序的哈夫曼樹同時受字符在文本中的排列順序和字符的權重影響
*********************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define OFFSET 35
typedef struct Node{
char value;
int weight;
}Node;
struct{
Node nodes[200];
int size;
} Nodes; // 全部的節點數據存儲在這里
typedef struct TreeNode{
Node *node;
struct TreeNode *parent,*left,*right;
}TreeNode;
struct{
TreeNode nodes[200];
int size;
} TreeNodes; // 其實沒必要存儲,但有得管理是好事
/*********************************************************************************
* 鏈表需要
********************************************************************************/
typedef struct Elem{
TreeNode *tNode;
struct Elem *prev,*next;
}Elem;
struct{
Elem elems[200];
int size;
} Elems; // 全部的鏈表數據存儲在這里 ( 不銷毀, 但會覆蓋 )
// 定義鏈表結構
typedef struct {
struct Elem *first;
struct Elem *last;
int size;
} LinkList;
// 初始化鏈表
void initLinkList( LinkList *list ){
list->size = 0;
}
// 獲取表長
int getSizeL(LinkList *list){
return list->size;
}
void addFirst(LinkList *list,Elem *elem){
if( !getSizeL(list) ){
list->first = elem;
list->last = elem;
}
else {
elem->prev = list->last;
elem->next = list->first;
list->last->next = elem;
list->first->prev = elem;
list->first = elem;
}
list->size++;
}
// 添加元素
void addLast(LinkList *list,Elem *elem){
if( !getSizeL(list) ){
list->first = elem;
list->last = elem;
} else {
elem->prev = list->last;
elem->next = list->first;
list->last->next = elem;
list->first->prev = elem;
list->last = elem;
}
list->size++;
}
Elem * getElem(LinkList *list, int index){
int i ;
Elem *elem;
// 逐項訪問
if ( index > list->size/2 ){
elem = list->last;
for ( i = list->size-1 ; i >= index ; i-- ){
if( i == index ){
return elem;
}
elem = elem->prev;
}
} else {
elem = list->first;
for ( i = 0 ; i <= index ; i++ ){
if( i == index ){
return elem;
}
elem = elem->next;
}
}
}
// 移除元素
void removeIndexL(LinkList *list, int index){
Elem *elem = getElem(list, index);
elem->prev->next = elem->next;
elem->next->prev = elem->prev;
if( index == 0 ){
list->first = elem->next;
}
list->size--;
}
void removeElemL(LinkList *list, Elem *e){
int i;
Elem *elem = list->first;
// while(e != elem ){
// elem = elem->next;
// }
// elem->prev->next = elem->next;
// elem->next->prev = elem->prev;
// if( list->first == elem ){
// list->first = elem->next;
// }
int flag = 0;
for ( i = 0 ; i < list->size ; i++ ){
if( elem == e ){
elem->prev->next = elem->next;
elem->next->prev = elem->prev;
if( list->first == elem ){
list->first = elem->next;
}
if( list->last == elem ){
list->last = elem->prev;
}
flag = 1;
}
elem = elem->next;
}
if(!flag) printf("沒能完成任務!!!\n");
list->size--;
}
/*********************************************************************************
* 鏈表需要
********************************************************************************/
void init(){
Nodes.size = 0;
Elems.size = 0;
TreeNodes.size = 0;
}
// 新建節點,返回節點指針
Node *createNode(char value,int weight){
Node *p = Nodes.nodes+Nodes.size;
p->value = value;
p->weight = weight;
Nodes.size++;
return p;
}
TreeNode *createTreeNode(Node *node){
TreeNode *p = TreeNodes.nodes+TreeNodes.size;
p->node = node;
p->left = NULL;
p->parent = NULL;
p->right = NULL;
TreeNodes.size++;
return p;
}
Elem *createElem( TreeNode *node ){
Elem *p = Elems.elems+Elems.size;
p->tNode = node;
Elems.size++;
if( Elems.size == 200 ) Elems.size = 0; // 注意不能超過200,否則數據錯誤
return p;
}
// 返回獲取最小節點
Elem *removeMin(LinkList *list){
Elem *elem = list->first;
Elem *p = elem;
int minWeight = p->tNode->node->weight ,w;
// while( elem->next != list->first ){
// elem = elem->next;
// w = elem->tNode->node->weight;
// if( minWeight > w ){
// p = elem;
// minWeight = w;
// }
// }
// removeElemL(list,p);
int i;
for ( i = 0 ; i < list->size ; i++ ){
elem = elem->next;
w = elem->tNode->node->weight;
if(minWeight>w){
p = elem;
minWeight = w;
}
}
removeElemL(list,p);
return p;
}
char path[200] = {'0'}; // 字符串存儲路徑
void printBaseNodes(TreeNode *tNode, int layer){
// 哈夫曼樹是完全二叉樹
// 判斷有無子節點即判斷是否為編碼節點
if( tNode->left == NULL ){
path[layer+1] = '\0';
printf("字符:%c --> 路徑: %s\n",tNode->node->value,path);
}
else {
path[layer+1] = '0';
printBaseNodes(tNode->left,layer+1);
path[layer+1] = '1';
printBaseNodes(tNode->right,layer+1);
}
}
void main(){
init(); //全局變量初始化在這里
int i = 0;
char c,hash[95];
LinkList list;
memset(hash,0,sizeof(hash)); //置零
while((c=getchar())!='\n'){
// 最好不要輸入空格!!!
hash[c-OFFSET]++;
}
printf("---------------------------------\n");
initLinkList(&list); // 初始化鏈表
for( i = 0 ; i < 95 ; i++ ){
if( !hash[i] ) continue;
printf("字符:%c --> 路徑: %d\n",i+OFFSET,hash[i]);
Node *node = createNode(i+OFFSET,hash[i]);
TreeNode *tNode = createTreeNode(node);
Elem *elem = createElem(tNode);
addFirst(&list,elem); // 完美
}
// 走到這里已經完成了節點創建了
// 現在要將節點構建成一棵哈夫曼二叉樹
printf("---------------------------------\n");
if( !list.size ){
printf("非法輸入,即將退出程序!\n");
exit(1);
}
if( list.size == 1 ){
// 如果小於1的話不需要構建樹了
printf("字符:%c --> 編碼:%s\n",list.first->tNode->node->value,"0");
exit(0);
}
while( list.size > 1 ){
Elem *p1 = removeMin(&list);
printf("最小權值(1)-字符:%c --> 權重:%d\n",p1->tNode->node->value,p1->tNode->node->weight);
Elem *p2 = removeMin(&list);
printf("最小權值(2)-字符:%c --> 權重:%d\n",p2->tNode->node->value,p2->tNode->node->weight);
Node *p3Node = createNode(0,p1->tNode->node->weight + p2->tNode->node->weight); // 新建一個節點
TreeNode *p3tNode = createTreeNode(p3Node); // 新建一個樹節點
Elem *p3 = createElem(p3tNode); // 新建鏈表元素
p1->tNode->parent = p3->tNode; // 父節點指向p3
p2->tNode->parent = p3->tNode; // 父節點指向p3
p3->tNode->left = p1->tNode; // p1 權重最小,為左節點
p3->tNode->right= p2->tNode; // p2 權重最大,為右節點
// printf("權值:%d + %d --> %d\n",p1->tNode->node->weight,p2->tNode->node->weight,p1->tNode->node->weight + p2->tNode->node->weight);
printf("生成權值(3)-字符:%c --> 權重: %d\n",p3->tNode->node->value,p3->tNode->node->weight);
addFirst(&list,p3); // 添加到表頭可以優先取出(如果權重最小的話)
}
printf("哈夫曼樹構建完成 --> 表長:%d\n", list.size );
// 如果表長不為 1 , 那么就是有問題了
// 到此已經構建好了哈夫曼樹了
// 現在要寫路徑算法,輸出編碼
printf("---------------------------------\n");
TreeNode *root = list.last->tNode; // 獲取根節點,已經不需要鏈表了
// 測試
printf("根下左右葉 --> 左葉:%d --> 右葉:%d\n",root->left->node->weight,root->right->node->weight);
printBaseNodes(root,0);
}
運行結果
ASDFAFDGVCKJFSDHAJDC
---------------------------------
字符:A --> 路徑: 3
字符:C --> 路徑: 2
字符:D --> 路徑: 4
字符:F --> 路徑: 3
字符:G --> 路徑: 1
字符:H --> 路徑: 1
字符:J --> 路徑: 2
字符:K --> 路徑: 1
字符:S --> 路徑: 2
字符:V --> 路徑: 1
---------------------------------
最小權值(1)-字符:V --> 權重:1
最小權值(2)-字符:K --> 權重:1
生成權值(3)-字符: --> 權重: 2
最小權值(1)-字符:H --> 權重:1
最小權值(2)-字符:G --> 權重:1
生成權值(3)-字符: --> 權重: 2
最小權值(1)-字符: --> 權重:2
最小權值(2)-字符: --> 權重:2
生成權值(3)-字符: --> 權重: 4
最小權值(1)-字符:S --> 權重:2
最小權值(2)-字符:J --> 權重:2
生成權值(3)-字符: --> 權重: 4
最小權值(1)-字符:C --> 權重:2
最小權值(2)-字符:F --> 權重:3
生成權值(3)-字符: --> 權重: 5
最小權值(1)-字符:A --> 權重:3
最小權值(2)-字符: --> 權重:4
生成權值(3)-字符: --> 權重: 7
最小權值(1)-字符: --> 權重:4
最小權值(2)-字符:D --> 權重:4
生成權值(3)-字符: --> 權重: 8
最小權值(1)-字符: --> 權重:5
最小權值(2)-字符: --> 權重:7
生成權值(3)-字符: --> 權重: 12
最小權值(1)-字符: --> 權重:8
最小權值(2)-字符: --> 權重:12
生成權值(3)-字符: --> 權重: 20
哈夫曼樹構建完成 --> 表長:1
---------------------------------
根下左右葉 --> 左葉:8 --> 右葉:12
字符:H --> 路徑: 00000
字符:G --> 路徑: 00001
字符:V --> 路徑: 00010
字符:K --> 路徑: 00011
字符:D --> 路徑: 001
字符:C --> 路徑: 0100
字符:F --> 路徑: 0101
字符:A --> 路徑: 0110
字符:S --> 路徑: 01110
字符:J --> 路徑: 01111
--------------------------------
Process exited after 3.761 seconds with return value 24
請按任意鍵繼續. . .