vlambda博客
学习文章列表

C语言实现 —— 哈夫曼编码

今日一言:
永远不需要解释你自己,
因为喜欢你的人不需要
不喜欢你的人不相信。

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 *listint 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 *listint 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;
            iflist->first == elem ){
                list->first = elem->next;
            }
            iflist->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);
    }

    iflist.size == 1 ){
        // 如果小于1的话不需要构建树了
        printf("字符:%c --> 编码:%s\n",list.first->tNode->node->value,"0"); 
        exit(0);
    }

    whilelist.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
请按任意键继续. . .