哈夫曼树的C语言详解

简介: 哈夫曼树的C语言详解


哈夫曼树的C语言详解
1)一些名词的解释:
路径:在一棵树中,一个结点到另一个结点之间的通路,称为路径。图 1 中,从根结点到结点 a 之间的通路就是一条路径。
路径长度:在一条路径中,每经过一个结点,路径长度都要加 1 。例如在一棵树中,规定根结点所在层数为1层,那么从根结点到第 i 层结点的路径长度为 i - 1 。图 1 中从根结点到结点 c 的路径长度为 3。
结点的权:给每一个结点赋予一个新的数值,被称为这个结点的权。
结点的带权路径长度:指的是从根结点到该结点之间的路径长度与该结点的权的乘积
2)哈夫曼树的定义:
当用 n 个结点(都做叶子结点且都有各自的权值)试图构建一棵树时,如果构建的这棵树的带权路径长度最小,称这棵树为“最优二叉树”,有时也叫“赫夫曼树”或者“哈夫曼树”。

在构建哈弗曼树时,要使树的带权路径长度最小,只需要遵循一个原则,那就是:权重越大的结点离树根越近。在图 1 中,因为结点 a 的权值最大,所以理应直接作为根结点的孩子结点。
3)构造过程:
对于给定的有各自权值的 n 个结点,构建哈夫曼树有一个行之有效的办法:
在 n 个权值中选出两个最小的权值,对应的两个结点组成一个新的二叉树,且新二叉树的根结点的权值为左右孩子权值的和;
在原有的 n 个权值中删除那两个最小的权值,同时将新的权值加入到 n–2 个权值的行列中,以此类推;
重复 1 和 2 ,直到所以的结点构建成了一棵二叉树为止,这棵树就是哈夫曼树。
4)算法实现:
构建哈夫曼树时,需要每次根据各个结点的权重值,筛选出其中值最小的两个结点,然后构建二叉树。

查找权重值最小的两个结点的思想是:从树组起始位置开始,首先找到两个无父结点的结点(说明还未使用其构建成树),然后和后续无父结点的结点依次做比较,有两种情况需要考虑:
如果比两个结点中较小的那个还小,就保留这个结点,删除原来较大的结点;
如果介于两个结点权重值之间,替换原来较大的结点;


#include<iostream>
#include<string.h>
#define  MAX 10000 
/*
请输入一段文字:this*program*is*my*favourite
字符和权值信息如下
字符:t  权值:2
字符:h  权值:1
字符:i  权值:3
字符:s  权值:2
字符:*  权值:4
字符:p  权值:1
字符:r  权值:3
字符:o  权值:2
字符:g  权值:1
字符:a  权值:2
字符:m  权值:2
字符:y  权值:1
字符:f  权值:1
字符:v  权值:1
字符:u  权值:1
字符:e  权值:1
********************************
字符编码为:
t:1000
h:11010
i:001
s:1001
*:011
p:11011
r:010
o:1010
g:11100
a:1011
m:1100
y:11101
f:11110
v:11111
u:0000
e:0001
文字编码为:
100011010001100101111011010101011100010101111000110011001011110011101011111101011111111010000001000110000001
********************************
译码:
请输入要译码的二进制字符串,输入'#'结束:100011010001100101111011010101011100010101111000110011001011110011101011111101011111111010000001000110000001#
译码为:
this*program*is*my*favourite
是否继续?(Y/N):
*/
using namespace std;
typedef struct {
    char letter, *code;
    int weight;
    int parent, lchild, rchild;
}HTNode, *HuffmanTree;
int n;
char coding[100];
int Min(HuffmanTree &HT, int i)
{
    int j;
    unsigned int k = MAX;
    int flag;
    for (j = 0; j <= i; ++j)
    {
        if (HT[j].weight < k && HT[j].parent == 0)//用父结点是否为0来判断此结点是否已经被选过  
        {
            k = HT[j].weight;
            flag = j;
        }
    }
    HT[flag].parent = 1;//作个标记,说明已经被选择了,因为在Select函数中要选择权值小的两个结点  
    return flag;
}
void Select(HuffmanTree &HT, int i, int &s1, int &s2)
{
    //在HT[1...i]中选择parent为0且权值最小的两个结点,其序号分别为s1,s2  
    //s1 <= s2  
    s1 = Min(HT, i);
    s2 = Min(HT, i);
}
void CreateHuffmanTree(HuffmanTree &HT, char t[], int w[])
{
    int m;
    int i, s1, s2;
    if (n <= 1)
        return;
    m = 2 * n - 1; //总共需要2n-1个节点
    HT = new HTNode[m + 1];//开辟空间
    for (i = 0; i<n; i++)
    {
        HT[i].code = '\0';
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
        HT[i].letter = t[i];
        HT[i].weight = w[i];
    }
    for (i = n; i <= m; i++)
    {
        HT[i].code = '\0';
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
        HT[i].letter = ' ';
        HT[i].weight = 0;
    }
    cout << "********************************" << endl;
    for (i = n; i<m; i++)
    {
        Select(HT, i - 1, s1, s2);//在n个数中找出权值最小的两个

        HT[s1].parent = i;
        HT[s2].parent = i;//将他们两个的parent节点设置为i;

        HT[i].lchild = s1;
        HT[i].rchild = s2;//把这两个分别当作左右节点
        HT[i].weight = HT[s1].weight + HT[s2].weight;//他们两个的双亲为他们两个的和;

    }
}
void CreatHuffmanCode(HuffmanTree HT)
{
    int start, c, f;
    int i;
    char *cd = new char[n];
    cd[n - 1] = '\0';
    cout << "字符编码为:" << endl;
    for (i = 0; i<n; i++)
    {
        start = n - 1;
        c = i;
        f = HT[i].parent;
        while (f != 0){
            --start;
            if (HT[f].lchild == c){
                cd[start] = '0';
            }
            else{
                cd[start] = '1';
            }
            c = f;
            f = HT[f].parent;
        }
        HT[i].code = new char[n - start];
        strcpy(HT[i].code, &cd[start]);
        cout << HT[i].letter << ":" << HT[i].code << endl;
    }
    delete cd;
}
void HuffmanTreeYima(HuffmanTree HT, char cod[], int b)           //译码
{
    char sen[100];
    char temp[50];
    char voidstr[] = " ";       //空白字符串
    int t = 0;
    int s = 0;
    int count = 0;
    for (int i = 0; i<b; i++)
    {
        temp[t++] = cod[i];     //读取字符
        temp[t] = '\0';        //有效字符串
        for (int j = 0; j<n; j++){        //依次与所有字符编码开始匹配
            if (!strcmp(HT[j].code, temp)){                //匹配成功
                sen[s] = HT[j].letter;    //将字符保存到sen中
                s++;
                count += t;
                strcpy(temp, voidstr);                //将TEMP置空 
                t = 0;          //t置空
                break;
            }
        }
    }
    if (t == 0){     //t如果被置空了,表示都匹配出来了,打印译码
        sen[s] = '\0';
        cout << "译码为:" << endl;
        cout << sen << endl;
    }
    else{                             //t如果没有被置空 , 源码无法被完全匹配
        cout << "二进制源码有错!从第" << count + 1 << "位开始" << endl;
    }
}
int main()
{
    HuffmanTree HT;
    char a[100], buff[1024], p;//a为存放字符 buff为输入的字符串 p为输入译码时的字符 
    int b[100];//存放权值信息 
    int i, j;
    int symbol = 1, x, k; //译码时做判断用的变量  
    cout << "请输入一段文字:";
    cin >> buff;
    int len = strlen(buff);
    for (i = 0; i<len; i++)
    {
        for (j = 0; j<n; j++)
        {
            if (a[j] == buff[i])
            {
                b[j] = b[j] + 1;
                break;
            }
        }
        if (j >= n)
        {
            a[n] = buff[i];
            b[n] = 1;
            n++;
        }
    }
    cout << "字符和权值信息如下" << endl;
    for (i = 0; i<n; i++)
    {
        cout << "字符:" << a[i] << "  权值:" << b[i] << endl;
    }
    CreateHuffmanTree(HT, a, b);
    CreatHuffmanCode(HT);
    cout << "文字编码为:\n";
    for (int i = 0; i < len; i++)
    {
        for (int j = 0; j < n; j++)
        {
            if (buff[i] == HT[j].letter)
            {
                cout << HT[j].code;
                break;
            }
        }
    }
    cout <<endl<< "********************************" << endl;
    cout << "译码:" << endl;
    while (1)
    {
        cout << "请输入要译码的二进制字符串,输入'#'结束:";
        x = 1;//判断是否有非法字符只能是0 1 
        k = 0;//作为循环变量来使coding【k】=输入的字符 
        symbol = 1;//判断是否输入结束 
        while (symbol){
            cin >> p;
            if (p != '1'&&p != '0'&&p != '#'){ //若存在其它字符,x设为0,表示输入的不是二进制
                x = 0;
            }
            coding[k] = p;
            if (p == '#')
                symbol = 0;  //#号结束标志
            k++;
        }
        if (x == 1){
            HuffmanTreeYima(HT, coding, k - 1);        //进行译码
        }
        else{
            cout << "有非法字符!" << endl;
        }
        cout << "是否继续?(Y/N):";
        cin >> p;
        if (p == 'y' || p == 'Y')
            continue;
        else
            break;
    }
    return 0;
}

相关文章
|
1月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
63 23
|
1月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
68 15
|
1月前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
61 24
|
1月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
66 16
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
37 3
|
1月前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
20 2
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】回文数判定(头歌实践教学平台习题)【合集】
算术运算于 C 语言仿若精密 “齿轮组”,驱动着数值处理流程。编写函数求区间[100,500]中所有的回文数,要求每行打印10个数。根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码。如果操作数是浮点数,在 C 语言中是不允许直接进行。的结果是 -1,因为 -7 除以 3 商为 -2,余数为 -1;注意:每一个数据输出格式为 printf("%4d", i);的结果是 1,因为 7 除以 -3 商为 -2,余数为 1。取余运算要求两个操作数必须是整数类型,包括。开始你的任务吧,祝你成功!
53 1
|
2月前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
94 10
|
2月前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
69 9
|
2月前
|
存储 Unix Serverless
【C语言】常用函数汇总表
本文总结了C语言中常用的函数,涵盖输入/输出、字符串操作、内存管理、数学运算、时间处理、文件操作及布尔类型等多个方面。每类函数均以表格形式列出其功能和使用示例,便于快速查阅和学习。通过综合示例代码,展示了这些函数的实际应用,帮助读者更好地理解和掌握C语言的基本功能和标准库函数的使用方法。感谢阅读,希望对你有所帮助!
62 8

热门文章

最新文章