数据结构与算法—哈夫曼编译码(有详细注释)

ps:代码是网上找的,删了一些,只保留了编译码,可以实现文件存入读取

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

struct HuffmanTree //用于表示哈夫曼树
{
    int weight;                 
    int Lchild, Rchild, Parent; 
    char letter;                
};

struct code  //用于表示哈夫曼编码
{
    char col[28];  //编码
    char letter;   //字符
} HC[28];          //数组HC存储这些编码

struct HuffmanTree *HT = NULL;  //指向哈夫曼树的指针,被用于动态分配内存以创建哈夫曼树
int N;                          //表示字符集的大小     
//整个哈夫曼树的结构和相关信息将通过HT指针来管理

void Input();                         
void Init();                          
void CreateHTree(char a[], int w[]);  
void Select(int n, int *s1, int *s2); 
void PutHuffmanTree();                
void CreateHuffmanCode();            
void Puteverycode();                  
int FILEhfmtree();                    
void Codetxt();                       
void DeCodetxt();                     
//用来事先声明函数,便于主函数调用                            

int main()
{
    int i, j;
    char command; //存储用户输入的命令
    system("color 0f"); //设置控制台的文本颜色为默认值
    while (1)
    {
        printf("哈夫曼编译码器\n\n");
        printf("O: 输入一段字符(包含26个大写字母和空格),统计出各个字符出现的次数\n\n");
        printf("I:初始化,建立哈夫曼树\n\n");
        printf("E:编码(Encoding)\n\n");
        printf("D:译码(Decoding)\n\n");
        printf("Q:退出程序\n\n");
        printf("请输入一个字母选择功能:");
        scanf("%c", &command);  //从用户输入中获取一个字符,存储到command变量中
        fflush(stdin);
        if ('Q' == command || 'q' == command)
            break;
        switch (command)
        {
        case 'o':
        case 'O':
            Input();
            break;
        case 'i':
        case 'I':
            Init();
            break;
        case 'e':
        case 'E':
            Codetxt();
            break;
        case 'd':
        case 'D':
            DeCodetxt();
            break;
        case 't':
        default:
            printf("输入的命令有误!\n");
            system("pause"); 
            system("cls");   
            break;
        }
    }
    system("pause");
    return 0;
}

void Input() //用于输入字符串,统计字符频度并将结果写入文件
{ 
    char str[1024], a[27] = " "; //str用于存储用户输入的字符串
    int Alph[27], i, j, flag = 1;//Alph用于存储字符频度,flag标志位初始化为1
    FILE *fp;                       //文件指针fp用于文件操作
    memset(Alph, 0, 27 * sizeof(int)); //将alph数组的所有元素初始化为0
    printf("请输入一串必须包含26个大写英文字母和空格的字符串\n");
    printf("Tip:输入以回车结束\n");
    gets(str); //获取用户输入
    i = 0;
    while (str[i]) //统计每个字符出现的次数
    {
        if ('A' <= str[i] && str[i] <= 'Z')
            Alph[str[i] - 'A' + 1] += 1;
        else if (str[i] == ' ')
            Alph[0] += 1;   //将结果存储在Alph数组中
        else
        {
            flag = 0; //检查输入合法性
            printf("输入字符串有不合条件的字符,请退出后重试!\n");
            break;
        }
        i++;
    }
    if (flag)
    {
        if (!Alph[0])
        {
            flag = 0;
            printf("输入的字符串不包含  空格\n");
        }
        for (i = 1; i < 27; i++)
            if (!Alph[i])
            {
                flag = 0;
                printf("输入的字符串不包含  %c\n", i + 64);
            }
        if (!flag)
            printf("请退出后重新输入\n");
    }
    if (flag)
    {
        fp = fopen("chartexfile.txt", "w");
        if (fp == NULL)
        {
            printf("创建文件chartexfile.txt错误\n");
            system("pause");
            system("cls");
            return;
        }
        fputs(str, fp);  //写入字符串到文件
        fclose(fp);
        printf("\n字符串已经写入chartexfile.txt文件\n\n");
        fp = fopen("charsumfile.txt", "w");
        if (fp == NULL)
        {
            printf("创建文件charsumfile.txt错误\n");
            system("pause");
            system("cls");
            return;
        }
        fprintf(fp, "%c%d", '@', Alph[0]);   //将空格和大写字母的频度写入文件,
		                  //@用来表示空格的字符,在哈夫曼编码中通常会用一个特殊的字符来表示空格,以便在编码和译码中能够准确处理空格字符    
        printf("字符  空格");              //打印字符频度信息
        for (i = 1; i < 27; i++)
            printf("%3c", i + 64);
        printf("\n频度  %4d", Alph[0]);
        for (i = 1; i < 27; i++)
        {
            fprintf(fp, "\n%c%d", i + 64, Alph[i]);
            printf("%3d", Alph[i]);  //在控制台输出各字符的出现次数
        }
        printf("\n\n各字符出现次数已经写入charsumfile.txt文件\n");
        fclose(fp); //关闭文件指针,释放资源

    }
    fflush(stdin); //清空输入缓冲区
} 

void Init()
{ 
    int i, flag, com; //循环变量i,标志位flag,选择命令com
    FILE *fp;
    char ABC[27], temp; //ABC存储字符集,temp用于临时存储字符
    int ABCnums[27];    //存储字符对应的频度
    int filenums[27];   //用来存储每个字符出现频率的数组
    system("cls");
    printf("1.输入字符从charsumfile.txt中读取频度\n\n");
    printf("2.输入字符和频度\n\n");
    printf("输入一个数选择功能:");
    flag = scanf("%d", &com);
    printf("\n请输入字符集大小N, 1<= N <=27\n");
    flag = scanf("%d", &N);
    if (com == 1)
    {
        fp = fopen("charsumfile.txt", "r");
        for (i = 0; i < 27; i++)
            fscanf(fp, "%c%d\n", &temp, &filenums[i]); //读取文件字符和频度
        fclose(fp);
        printf("\n请输入N个不同的大写字母或空格,输入一个按一下回车\n");
        for (i = 0; i < N; i++)
        {
            fflush(stdin);
            ABC[i] = getc(stdin); //获取输入,getc只能用于标准输入流
            while ((ABC[i] > 'Z' || ABC[i] < 'A') && ABC[i] != ' ') //检查输入是否正确
            {
                printf("输入的不是大写字母或空格,请重新输入\n");
                fflush(stdin);
                ABC[i] = getc(stdin);
            }
            if (ABC[i] == ' ') 
                ABCnums[i] = filenums[0];
            else
                ABCnums[i] = filenums[ABC[i] - 64];  //比如A在ASCII表中的值是65,为了将字符"A"映射到数组filenums的索引,需要减去64
        }                                            //这样'A'对应的ASCII值减去64就得到了filenums数组中对应的索引
    }
    else if (com == 2)
    {
        printf("\n输入大写字母或空格和频度例如\nA12\nC23\n\n");
        for (i = 0; i < N; i++)
        {
            fflush(stdin);
            ABC[i] = getc(stdin);
            scanf("%d", &ABCnums[i]);  //用户输入字符与频度的组合
        }
    }
    printf("字符  ");
    for (i = 0; i < N; i++)
    {
        if (ABC[i] == ' ')
            printf("空格 ");
        else
            printf("%-4c ", ABC[i]);
    }
    printf("\n频度  ");        
    for (i = 0; i < N; i++)
        printf("%-4d ", ABCnums[i]);
    printf("\n");               //打印字符集和对应的频度
    CreateHTree(ABC, ABCnums);  //调用该函数创建哈夫曼树
    PutHuffmanTree();           //打印哈夫曼树表格
    fp = fopen("hfmTree.txt", "w");
    if (fp == NULL)
    {
        printf("hfmTree.txt文件创建错误\n");
        system("pause");
        system("cls");
        return;
    }
    fprintf(fp, "%-6s%-6s%-6s%-8s%-8s%-6s\n", "编号", "字符", "权值", "左孩子", "右孩子", "父亲");  //将哈夫曼树的信息输出到文件
    for (i = 1; i <= N; i++)
    {
        if (HT[i].letter == ' ')
            fprintf(fp, "%-6d%-6c%-6d%-8d%-8d%-6d\n", i, '@', HT[i].weight, HT[i].Lchild, HT[i].Rchild, HT[i].Parent);
        else
            fprintf(fp, "%-6d%-6c%-6d%-8d%-8d%-6d\n", i, HT[i].letter, HT[i].weight, HT[i].Lchild, HT[i].Rchild, HT[i].Parent);
    }
    for (i; i <= 2 * N - 1; i++)
        fprintf(fp, "%-6d%-6c%-6d%-8d%-8d%-6d\n", i, HT[i].letter, HT[i].weight, HT[i].Lchild, HT[i].Rchild, HT[i].Parent);
    fclose(fp);
    printf("\n哈夫曼树表已经写入hfmTree.txt中\n");
    CreateHuffmanCode();  //调用该函数创建哈夫曼编码
    Puteverycode();       //调用该函数,将每个字符的哈夫曼编码输出到控制台
    system("pause");
    system("cls");
    fflush(stdin);
}

void CreateHTree(char a[], int w[])
{ 
    struct HuffmanTree *pt;  //定义指向struct HuffmanTree结构体的指针pt,用于遍历哈夫曼树数组
    int m, i, s1, s2;   //i为循环变量
    m = 2 * N - 1;      //表示哈夫曼树数组的大小
    s1 = 1;             //s1和s2用于记录选择的两个权值最小的节点
    s2 = 2;
    HT = (struct HuffmanTree *)malloc(sizeof(struct HuffmanTree) * (m + 1));
    //使用malloc函数为哈夫曼树数组HT分配内存空间,大小为sizeof(struct HuffmanTree) * (m + 1)
    pt = HT;                                 
    pt++;               //初始化节点信息                               
    for (i = 1; i <= N; i++, pt++, w++, a++) 
    {   //通过遍历循环,为前n各节点赋初值,权值来自频度数组w[],字符来自输入的字符数组a[]
        pt->weight = *w;
        pt->Lchild = 0;
        pt->Rchild = 0;
        pt->Parent = 0;
        pt->letter = *a;
    }
    for (i; i <= m; i++, pt++) 
    {   //对于超过n的节点,将其权值、孩子、父亲等属性初始化为0
        pt->weight = 0;
        pt->Lchild = 0;
        pt->Rchild = 0;
        pt->Parent = 0;
        pt->letter = 0;
    }
    for (i = N + 1; i <= m; i++) 
    {//从第n+1个节点开始,选择权值最小的两个节点,并将他们作为左右孩子构建新的节点,该新节点的权值为左右孩子权值之和
        Select(i - 1, &s1, &s2); 
        HT[i].Lchild = s1;
        HT[i].Rchild = s2;
        HT[i].weight = HT[s1].weight + HT[s2].weight;
        HT[s1].Parent = i;
        HT[s2].Parent = i;
    }
}

void Select(int n, int *s1, int *s2) //在构建哈夫曼树的过程中,这个函数会被调用多次,每次选择两个最小权值的节点进行合并
{ 
    int i = 1, temp;  //i作为循环变量,初始值为1,用于遍历哈夫曼树数组。temp用于在交换s1和s2时暂存其中一个的值
    while (HT[i].Parent != 0 && i <= n)  //寻找哈夫曼树数组中第一个parent属性为0的节点,即尚未构建父节点的节点
        i++;
    if (i == n + 1) //如果没有,函数直接返回
        return;
    *s1 = i++;     //将当前找到的第一个没有父节点的节点的索引记录在s1中,并将i自增                 
    while (HT[i].Parent != 0 && i <= n) 
        i++;
    if (i == n + 1)
        return;
    *s2 = i++;                        
    if (HT[*s1].weight > HT[*s2].weight)
    {  //交换s1和s2,确保s1对应的节点权值小于等于s2对应的节点权值
        temp = *s1;                     
        *s1 = *s2;
        *s2 = temp;
    }
    for (; i <= n; i++)  //继续遍历剩余的节点,找到两个权值最小的节点
    {  
        if (HT[i].Parent == 0)  //确保它们没有父节点
        {
            if (HT[i].weight < HT[*s1].weight)   
            {
                *s2 = *s1;
                *s1 = i;
            }
            else if (HT[i].weight < HT[*s2].weight)
                *s2 = i;
        }
    }
}

void PutHuffmanTree()  //打印哈夫曼树的表格
{                      //此函数不接受任何参数,它使用全局变量HT来访问哈夫曼树
    int i, m = 2 * N - 1;
    printf("\n输出哈夫曼树表\n");
    printf("\n%-6s%-6s%-6s%-8s%-8s%-6s\n", "编号", "字符", "权值", "左孩子", "右孩子", "父亲");
    for (i = 1; i <= N; i++) //遍历哈夫曼树的节点,并打印每个节点的信息
    {
        if (HT[i].letter == ' ')
            printf("%-6d%-6s%-6d%-8d%-8d%-6d\n", i, "空格", HT[i].weight, HT[i].Lchild, HT[i].Rchild, HT[i].Parent);
        else
            printf("%-6d%-6c%-6d%-8d%-8d%-6d\n", i, HT[i].letter, HT[i].weight, HT[i].Lchild, HT[i].Rchild, HT[i].Parent);
    }
    for (i; i <= m; i++)//打印父节点,继续打印剩余的节点信息,这些节点对应于哈夫曼树的内部(父)节点
        printf("%-6d%-6c%-6d%-8d%-8d%-6d\n", i, HT[i].letter, HT[i].weight, HT[i].Lchild, HT[i].Rchild, HT[i].Parent);
    printf("\n");
}

void CreateHuffmanCode()  //为每个字符生成对应的哈夫曼编码
{ 
    int start, i, p, f;
    char col[28];
    memset(HC, 0, sizeof(struct code) * 28); //用memset函数将全局变量HC数组初始化为0。HC是一个结构数组,用于存储字符及其对应的哈夫曼编码
    for (i = 1; i <= N; i++)   //for循环遍历哈夫曼树的每个叶子节点,这些叶子节点对应于输入的字符
    {                          //对于每个叶子节点,函数通过迭代向上遍历树来生成其哈夫曼编码
        memset(col, 0, sizeof(col)); //用一个辅助数组col来存储编码,初始化为0
        start = N - 1;  //从当前节点开始
        p = f = i;      //向其父节点移动
        while (HT[f].Parent != 0)  
        {
            f = HT[f].Parent;
            if (HT[f].Lchild == p)    //根据是左孩子还是右孩子
                col[--start] = '0';   //在col数组的开头插入对应的0或1
            else if (HT[f].Rchild == p)
                col[--start] = '1';
            p = f;
        }
        HC[i].letter = HT[i].letter;  //将当前字符存储在 HC[i].letter
        strcpy(HC[i].col, col + start); //将col数组的内容复制到HC结构体数组中,即将生成的哈夫曼编码存储在HC[i].col
    }
}

void Puteverycode()  //将每个字符的哈夫曼编码输出到控制台
{ 
    int i;
    printf("%-6s%-8s\n", "字符", "编码");
    for (i = 1; i <= N; i++)
    {
        if (HC[i].letter == ' ')  //通过for循环遍历哈夫曼编码数组HC中的每个元素
            printf("%-6s%-8s\n", "空格", HC[i].col);
        else
            printf("%-6c%-8s\n", HC[i].letter, HC[i].col);
    }
}

int FILEhfmtree() //从名为"hfmTree.txt"的文件中读取哈夫曼树的信息并初始化哈夫曼树结构(HT)
{ 
    FILE *fp; //文件打开
    int i, temp;
    char a;
    fp = fopen("hfmTree.txt", "r"); //以只读模式"r"打开"hfmTree.txt"文件
    if (fp == NULL)
        return 0; //文件打开失败
    getc(fp); //使用getc(fp)从文件中读取第一个字符
    if (feof(fp)) //f开头表示文件操作,检查文件的结束标志,没有检查到就返回0,检查到了结束标志就返回非0.
        return 0;
    i = 1;
    while (1)
    {
        fseek(fp, 42 * i + 6, SEEK_SET); //将指针fp指向文件开头
        a = fgetc(fp); //使用fgetc(fp)从文件中读取一个字符
        if (!a)
            break;
        i++;
    }
    N = i - 1; //根据计算得到的节点数N为哈夫曼树HT分配内存
    HT = (struct HuffmanTree *)malloc(sizeof(struct HuffmanTree) * (2 * N)); 
    for (i = 1; i <= 2 * N - 1; i++)
    {
        fseek(fp, 42 * i + 6, SEEK_SET);
        a = fgetc(fp);
        fscanf(fp, "%d%d%d%d", &HT[i].weight, &HT[i].Lchild, &HT[i].Rchild, &HT[i].Parent); //读取每个节点的信息
        if (a == '@')  
            HT[i].letter = ' ';  //将读取到的值分配给哈夫曼树结构的相应字段
        else
            HT[i].letter = a;
    }
    fclose(fp); //关闭文件
    return 1;   //返回1表示成功
}

void Codetxt() //对输入的文本进行哈夫曼编码,并将编码结果存储到文件"CodeFile.txt"中
{ 
    char str[1024];
    char codehf[2048];
    int i, j;
    FILE *fp;
    system("cls");
    if (NULL == HT)  //检查哈夫曼树的存在
    {
        printf("\n当前内存中没有哈夫曼树,是否从hfmTree.txt文件读取(Y/N)\n");
        fflush(stdin);
        if ('Y' == getc(stdin))
        {
            if (FILEhfmtree() == 0)
            {
                printf("\nhfmTree.txt中没有哈夫曼树,请使用功能I初始化\n");
                system("pause");
                system("cls");
                fflush(stdin);
                return;
            }
        }
        else
        {
            printf("\n没有构建哈夫曼树,无法进行编码\n");
            system("pause");
            system("cls");
            fflush(stdin);
            return;
        }
    }
    CreateHuffmanCode();   //使用该函数为哈夫曼树中的每个字符生成相应的哈夫曼编码
    if (NULL == (fp = fopen("ToBeTran.txt", "w")))
    {
        printf("\nToBeTran.txt文件创建错误\n");
        system("pause");
        system("cls");
        return;
    }
    printf("\n请使用这些字符:  ");
    for (i = 1; i <= N; i++)
    {
        if (HC[i].letter == ' ')
            printf("%-5s", "空格");
        else
            printf("%-5c", HC[i].letter);
    }
    printf("\n\n请输入一串要编码的文本\n");
    fflush(stdin);  //清除输入缓存
    gets(str);      //使用gets函数获取用户输入的一串文本
    fputs(str, fp); //将用户输入的文本写入"ToBeTran.txt"文件
    fclose(fp);     //关闭文件
    printf("\n输入的文本已经存入ToBeTran.txt文件\n");
    memset(codehf, 0, sizeof(codehf));
    for (i = 0; str[i]; i++) 
    {
        for (j = 1; j <= N; j++)
            if (str[i] == HC[j].letter) //遍历输入的文本,查找每个字符在哈夫曼编码表中对应的编码,将编码结果存储在codehf数组中
            {
                strcat(codehf, HC[j].col);
                break;
            }
        if (j == N + 1)
        {
            printf("\n有不能编码的字符:%c\n退出后重试\n", str[i]);
            system("pause");
            system("cls");
            return;
        }
    }
    printf("\n编码结果:\n");
    for (i = 0; codehf[i]; i++)
    {
        printf("%c", codehf[i]);
        if (i % 50 == 49)
            printf("\n"); //输出编码结果到控制台,以每行50个字符的方式输出编码结果
    }
    if (NULL == (fp = fopen("CodeFile.txt", "w"))) //打开"CodeFile.txt"文件失败
    {
        printf("\nCodeFile.txt文件创建错误\n");
        system("pause");
        system("cls");
        return;
    }
    fputs(codehf, fp); //将编码结果写入"CodeFile.txt"文件
    fclose(fp);
    printf("\n编码结果已经存入CodeFile.txt文件\n");
    system("pause");
    system("cls"); //清空屏幕
    fflush(stdin); //刷新输入缓冲区
}

void DeCodetxt() //对输入的哈夫曼编码进行译码,并将译码结果存储到文件"TextFile.txt"中
 { 
    char str[1024];
    char codehf[2048];
    int i, j, start, temp;
    FILE *fp;
    system("cls");
    if (NULL == HT)
    {
        printf("\n当前内存中没有哈夫曼树,是否从hfmTree.txt文件读取(Y/N)\n");
        fflush(stdin);
        if ('Y' == getc(stdin))
        {
            if (FILEhfmtree() == 0)
            {
                printf("\nhfmTree.txt中没有哈夫曼树,请使用功能I初始化\n");
                system("pause");
                system("cls");
                fflush(stdin);
                return;
            }
        }
        else
        {
            printf("\n没有构建哈夫曼树,无法进行译码\n");
            system("pause");
            system("cls");
            fflush(stdin);
            return;
        }
    }
    CreateHuffmanCode();  //使用该函数为哈夫曼树中的每个字符生成相应的哈夫曼编码
    printf("\n当前的哈夫曼树中:\n");
    Puteverycode();       //使用该函数在控制台上输出当前哈夫曼树的字符及对应的编码
    if (NULL == (fp = fopen("CodeFile.txt", "r")))
    {
        printf("\nCodeFile.txt文件读取错误\n");
        system("pause");
        system("cls");
        return;
    }
    fgets(codehf, 2048, fp); //从文件中读入字符串
    fclose(fp);
    printf("\nCodeFile.txt文件源码中: \n");
    for (i = 0; codehf[i]; i++)
    {
        printf("%c", codehf[i]); //输出CodeFile.txt文件源码
        if (i % 50 == 49)
            printf("\n");
    }
    memset(str, 0, sizeof(str)); //使用memset函数清零str数组,用于存储译码结果
    i = 0;                       //初始化i为0,用于遍历哈夫曼编码字符串
    for (j = 0;; j++)            //初始化j为0,用于遍历译码结果
    {
        start = 2 * N - 1;
        for (i; codehf[i]; i++)
        { //使用循环遍历哈夫曼编码字符串,根据当前字符是"0"还是"1",在哈夫曼树中找到对应的子节点
            if (codehf[i] == '0')                
                temp = HT[start].Lchild;
            else if (codehf[i] == '1')
                temp = HT[start].Rchild;
            start = temp;
            if (HT[start].Lchild == 0 && HT[start].Rchild == 0)  //左右孩子都为0,则找到叶子节点
            {
                i++;
                break;
            }
        }
        str[j] = HT[start].letter; //将叶子节点对应的字符存入str数组中,并将i移动到下一个字符位置
        if (codehf[i] == 0)
            break;
    }
    printf("\n\n译码结果:\n");
    for (i = 0; str[i]; i++)
    {
        printf("%c", str[i]);   
        if (i % 50 == 49)  //以每行50个字符的方式输出译码结果
            printf("\n");
    }
    fputs(str, fp);  //将译码结果写入文件"TextFile.txt"
    fclose(fp);
    printf("\n\n译码结果已经存入TextFile.txt文件中\n");
    system("pause");
    system("cls"); //清空屏幕
    fflush(stdin); //刷新输入缓存区
}

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值