9.数据结构与算法之哈夫曼树

当用 n 个结点(都做叶子结点且都有各自的权值)试图构建一棵树时,如果构建的这棵树的带权路径长度最小,称这棵树为“最优二叉树”,或者“哈夫曼树”。
在构建哈夫曼树时,要使树的带权路径长度最小,只需要遵循一个原则,那就是:权重越大的结点离树根越近。
构建哈夫曼树时,首先需要确定树中结点的构成。由于哈夫曼树的构建是从叶子结点开始,不断地构建新的父结点,直至树根,所以结点中应包含指向父结点的指针。但是在使用哈夫曼树时是从树根开始,根据需求遍历树中的结点,因此每个结点需要有指向其左孩子和右孩子的指针。
构建哈夫曼树时,需要每次根据各个结点的权重值,筛选出其中值最小的两个结点,然后构建二叉树。
查找权重值最小的两个结点的思想是:从树组起始位置开始,首先找到两个无父结点的结点(说明还未使用其构建成树),然后和后续无父结点的结点依次做比较,有两种情况需要考虑:
如果比两个结点中较小的那个还小,就保留这个结点,删除原来较大的结点;
如果介于两个结点权重值之间,替换原来较大的结点;

代码如下:

#include <stdio.h>
#include <stdlib.h>

//最大读入的文本文件的字符长度
#define MAXSIZE 100

typedef struct NODE
{
    char c;
    //结点权重
    int weight;
    //记录结点表示的二进制,左0右1
    int binary;
    //是否已经创建该结点,1为创建,0为未创建
    int flag;

    struct NODE *lchild, *rchild, *parent;

} NODE, *PNODE;

typedef struct
{
    int valuse[MAXSIZE];
    int top;
} STACK;

/**
 * 栈的相关操作
 **/
void initStack(STACK &stack)
{
    stack.top = 0;
}

void push(STACK &stack, int value)
{
    stack.valuse[stack.top++] = value;
}

int pop(STACK &stack)
{
    if (stack.top == 0)
        return -1;
    stack.top--;
    return stack.valuse[stack.top];
}

//初始化树结点
void initNode(PNODE node)
{
    node->lchild = NULL;
    node->parent = NULL;
    node->rchild = NULL;
    node->flag = 0;
    node->weight = 0;
    node->c = -1;
    node->binary = 0;
}

/**
 * 创建结点
 * 
 * param 新节点的权重
 **/
PNODE createNode(int weight)
{
    PNODE node = (PNODE)malloc(sizeof(NODE));
    if (node)
    {
        initNode(node);
        node->weight = weight;
    }
    return node;
}

/**
 * 进行字符的编码
 * 遍历所有的叶子结点,找到与字符相对应的叶子结点,向上遍历,将路径入栈
 * 
 * param c 欲编码的字符
 * param childrenNodes 叶子结点的集合
 * param lenOfNodes 叶子结点的个数
 * return 返回保存该叶子结点路径的栈
 **/
STACK charEncode(char c, PNODE childrenNodes, int lenOfNodes)
{
    STACK stack;
    initStack(stack);
    for (int i = 0; i < lenOfNodes; i++)
    {
        if (c == childrenNodes[i].c)
        {
            PNODE tmp = &childrenNodes[i];
            while (tmp->parent != NULL)
            {
                push(stack, tmp->binary);
                tmp = tmp->parent;
            }
            break;
        }
    }
    return stack;
}

/**
 * 进行字符串的编码
 * 
 * param str 欲编码的字符串
 * param childrenNodes 哈夫曼树的叶子结点的集合
 * param lenOfNodes 叶子结点的个数
 * return 返回编码后的二进制字符串
 **/
char *strEncode(char *str, PNODE childrenNodes, int lenOfNodes)
{
    char *result = (char *)malloc(sizeof(char) * MAXSIZE * lenOfNodes);
    int len = 0;
    while (*str != '\0')
    {
        STACK stack = charEncode(*str, childrenNodes, lenOfNodes);
        while (stack.top > 0)
        {
            result[len++] = pop(stack) + '0';
        }
        str++;
    }
    result[len] = '\0';
    return result;
}

/**
 * 获取权重最小的结点
 * 
 * param nodes 叶子结点的集合
 * param lenOfNodes 叶子结点的个数
 * return 返回该叶子结点的地址,若所有的结点都已创建,返回NULL
 **/
PNODE getMinWeightNode(PNODE nodes, int lenOfNodes)
{
    PNODE node;
    int min = 0, i;

    //对已创建过的结点进行过滤
    while (min < lenOfNodes)
    {
        if (nodes[min].flag == 0)
        {
            break;
        }
        min++;
    }

    if (min == lenOfNodes)
    {
        return NULL;
    }

    //查找未创建的结点中权重最小的结点
    for (i = min + 1; i < lenOfNodes; i++)
    {
        if (nodes[i].flag == 0 && nodes[i].weight < nodes[min].weight)
        {
            min = i;
            continue;
        }
    }

    nodes[min].flag = 1;
    return &nodes[min];
}

/**
 * 根据叶子结点创建哈夫曼树
 * 
 * param nodes 叶子结点的集合
 * param lenOfNode 叶子结点的个数
 * param childNode 子结点的一个
 **/
PNODE createHuffmanTree(PNODE nodes, int lenOfNodes, PNODE childNode)
{
    PNODE minWeightNode, parentNode;

    minWeightNode = getMinWeightNode(nodes, lenOfNodes);

    if (!minWeightNode)
        return childNode;

    if (!childNode)
    {
        parentNode = minWeightNode;
    }
    else
    {
        parentNode = createNode(childNode->weight + minWeightNode->weight);

        if (childNode->weight < minWeightNode->weight)
        {
            parentNode->lchild = childNode;
            parentNode->rchild = minWeightNode;
        }
        else
        {
            parentNode->rchild = childNode;
            parentNode->lchild = minWeightNode;
        }
        parentNode->lchild->binary = 0;
        parentNode->rchild->binary = 1;

        childNode->parent = minWeightNode->parent = parentNode;
    }

    createHuffmanTree(nodes, lenOfNodes, parentNode);
}

/**
 * 将大写字母转成小写字母
 * 若区分大小写,哈夫曼树的度增加一倍,编码效率降低
 * 
 * param c 传入的字符
 * return 若传入的字符是大写字母则转换成小写字母,如是其他字符不做更改
**/
char charTolowercase(char c)
{
    if (c >= 'A' && c <= 'Z')
    {
        c += 'a' - 'A';
    }
    return c;
}

/**
 * 根据字符出现频率创建哈夫曼树
 * 返回哈夫曼树根结点
 * 
 * param buff 用来保存读取的字符串的数组
 * param nodes 用来记录所有的叶子结点
 * param lenOfNodes 用来保存叶子结点的个数
 **/
PNODE readFromSource(const char *filePath, char *buff, PNODE childrenNodes, int &lenOfNodes)
{

    //记录字符串长度
    int lenOfStr = 0, i;

    //记录当前读取的字符
    char c;

    FILE *file = fopen(filePath, "rb");

    if (file == NULL)
    {
        puts("Can't find source file!");
        exit(0);
    }

    //一个字符一个字符读入本地文本
    c = fgetc(file);
    while (!feof(file))
    {
        c = charTolowercase(c);
        //初始化结点
        initNode(&childrenNodes[lenOfNodes]);

        buff[lenOfStr++] = c;
        for (i = 0; i < lenOfNodes; i++)
        {
            if (childrenNodes[i].c == c)
            {
                childrenNodes[i].weight++;
                break;
            }
        }
        if (i == lenOfNodes)
        {
            childrenNodes[lenOfNodes].c = c;
            childrenNodes[lenOfNodes++].weight++;
        }
        c = fgetc(file);
    }
    buff[lenOfStr] = '\0';

    fclose(file);
    return createHuffmanTree(childrenNodes, lenOfNodes, NULL);
}

/**
 * 将编码后的字符串存入本地文件
 * param filePath 欲存放的本地文件路径
 * param result 编码后的二进制结果
 **/
void writeResult(const char *filePath, char *result)
{
    FILE *fp = fopen(filePath, "wb");
    if (fputs(result, fp) >= 0)
    {
        printf("生成结果成功\r\n");
    }
    fclose(fp);
}

/**
 * 将编码后的二进制字符串进行解码
 * param str 欲解码的二进制字符串
 * param 哈夫曼树根结点
 * return 返回解码后的字符串
 * 
 * tips:哈夫曼树是根据原字符串构造而来,一棵树对应一种编码方式
 **/
char *strDecode(const char *str, PNODE TreeRoot)
{
    const char *tmp = str;
    char *result = (char *)malloc(sizeof(char) * MAXSIZE);
    //用来记录结果字符串的长度
    int len = 0;
    while (*tmp != '\0')
    {
        PNODE tmpNode = TreeRoot;
        while (tmpNode->lchild && tmpNode->rchild)
        {
            tmpNode = *tmp == '0' ? tmpNode->lchild : tmpNode->rchild;
            tmp++;
        }
        result[len++] = tmpNode->c;
    }
    result[len] = '\0';
    return result;
}

int main()
{
    char buff[MAXSIZE];

    NODE childrenNodes[MAXSIZE];

    int len = 0;

    //确保source.txt和exe处于同目录
    PNODE root = readFromSource("source.txt", buff, childrenNodes, len);

    writeResult("result.txt", strEncode(buff, childrenNodes, len));

    printf("%s", strDecode("11111111111110111111111110011101111111111011001110011111111101111111101111111011111101111101100111101010", root));

    return 0;
}

输出结果如下:

Can't find source file!

--------------------------------
Process exited after 0.04188 seconds with return value 0
请按任意键继续. . .

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值