数据结构实验六——赫夫曼树及赫夫曼编码算法实现(2021级zzu)

ps: 怠惰了,,晚上十点才开始写…

一、实验目的

熟悉掌握 Huffman 树的构造方法及 Huffman 编码的应用,了解 Huffman 树在通信、编码领域的应用过程。

二、实验内容

(1) 输入一段 100—200 字的英文短文,存入一文件 a 中。

(2) 写函数统计短文出现的字母个数 n 及每个字母的出现次数

(3)写函数以字母出现次数作权值,建 Haffman 树(n 个叶子),给出每个字母的Haffman 编码。

(4)用每个字母编码对原短文进行编码,码文存入文件 b 中。

(5) 用 Haffman 树对文件 b 中码文进行译码,结果存入文件 c 中,比较a,c 是否一致,以检验编码、译码的正确性。

三、问题描述

问题概况:统计文件 a 中一串短文字母个数以及每个字母出现次数,并且以字母出现次数作权值建 Huffman 树,给出每个字母的Huffman 编码,将原文进行编码存到文件 b;用 Huffman 树对 b 中码文进行译码结果存到文件 c 中。

四、数据结构定义

Huffman 树、字母编码采用顺序存储结构,数据元素类型整型和字符型。

五、算法思想及算法设计

题目:
(1) 输入一段 100—200 字的英文短文,存入一文件 a 中。
(2) 写函数统计短文出现的字母个数 n 及每个字母的出现次数
(3) 写函数以字母出现次数作权值,建 Haffman 树(n 个叶子),给出每个字母的Haffman 编码。
(4) 用每个字母编码对原短文进行编码,码文存入文件 b 中。
(5) 用 Haffman 树对文件 b 中码文进行译码,结果存入文件 c 中,比较
a,c 是否一致,以检验编码、译码的正确性。

思考:

  1. 针对 (1),要考虑英文短文是否为一个自然段(否则需要换行输入),通过一些基础的文件操作将其输入文件 a 中。
  2. 针对 (2),通过文件操作,得到字母个数和每个字母出现次数,次数存到一个整型数组 w 中,并且默认 w 从下标 0 开始存放 abcdef 等小写字母的次数,从下标 26开始存放 ABCDEF 等大写字母的次数,下标 52 存放空格字符的次数(这里没有考虑英文短文中的逗号句号等其他字符)。
  3. 针对 (3),这里借鉴了教材中的 Huffman 编码。短文中出现过的字母种类,按字母表上的先后,将其编码一一存放到 HC 二维数组中,并且输出。
  4. 针对 (4),利用 (3) 中的 HC 数组,对原文编码,通过文件操作输入到 b 中。
  5. 针对 (5), 通过文件操作获取 b 中编码,结合 Huffman 树,得到应有的字符,并将字符一一输入到文件 c 中。

<1> 输入一个短文到文件 a 中:

Status Puta()
{
    int i = 0, j = 0;
    char ch[6][1000]; // 这里默认短文不超过 6 行 1000 列,每个字母算一列
    for (i = 0; i < 6; i++)
    {
        fgets(ch[i], sizeof(ch[i]), stdin); // 必须输入六行,不足六行直接输入回车即可
    }
    FILE* fp = fopen("C:\\Users\\LENOVO\\Desktop\\a.txt", "w"); // 不同环境下运行此代码需要修改此地址
    while (j < i)
    {
        fputs(ch[j], fp);
        j++;
    }
    fclose(fp);
    return OK;
}

<2> 获得权重(字母出现次数):

Status GetWeight(int *w)
{
    FILE* fp = fopen("C:\\Users\\LENOVO\\Desktop\\a.txt", "r"); // 不同环境下运行此代码需要修改此地址
    if (fp == NULL)
    {
        printf("OPEN error!");
        return ERROR;
    }
    char ch;
    int i, j;
    while ((ch = fgetc(fp)) != EOF) // ch 获取文件 a 每个字符
    {
        i = 0;
        j = 0;
        if (ch == '\0');
        else if (ch == ' ') { w[52]++; continue; }
        else if ((int)ch >= 97 && (int)ch <= 122)
        {
            while (i < 26)
            {
                if (ch == 'a' + i) { w[i]++; break; } // 例:假如 i=1 执行此句,那么默认字符'b’出现次数 +1
                i++;
            }
        }
        else if ((int)ch >= 65 && (int)ch <= 90)
        {
            i = 26;
            while (i < 52)
            {
                if (ch == 'A' + j) { w[i]++; break; } // 意思同上
                i++;
                j++;
            }
        }
    }
    int n = 0;
    for (i = 0; i < 52; i++)
    {
        if (w[i] != 0) n++; // 这里不含空格字符的字母种类数,这是为了统计初始有几个 Huffman 树根节点
    }
    fclose(fp);
    return n;
}

<3> 输出字母总数和每个字母出现次数:

Status ReadW(int* w)
{
    int sum = 0, i = 0, j = 0, cnt = 0;
    while (i < 52)
    {
        sum = sum + w[i];
        i++;
    }
    i = 0;
    printf("This passage has %d words\n", sum); // 输出字母总数
    while (i < 26)
    {
        printf("%c: %d ", 'a' + i, w[i]); // 例:假如 i=2 执行此句,则输出 c 的出现次数
        cnt++;
        i++;
        if (cnt == 4) { 
            cnt = 0; 
            printf("\n"); 
        }
    }
    cnt = 0;
    printf("\n");
    while (i < 52)
    {
        printf("%c: %d ", 'A' + j, w[i]); // 意思同上
        i++;
        j++;
        cnt++;
        if (cnt == 4) { 
            cnt = 0; 
            printf("\n"); 
        }
    }
    return OK;
}

<4>Huffman 树:

void HuffmanCoding(HuffmanTree& HT, HuffmanCode& HC, int* w, int n)
{
    HuffmanTree p;
    int i, s1, m2, j = 0;
    
    if (n <= 1)
        return;
    
    int m = 2 * n - 1; // 有 n 个叶子结点的 Huffman 树有 2*n-1 个节点
    
    HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));
    
    for (i = 1; i <= n; ++i)
    {
        while (w[j] == 0)
        {
            // 由于 w 默认 52 个字母和 1 个空格字符,英文短文不一定全会出现这 53 个字符,
            // 所以将权重不为 0 的赋给 Huffman 树根节点
            j++;
        }
        
        HT[i].weight = w[j];
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
        j++;
    }
    
    for (; i <= m; ++i)
    {
        // 超过 n 的部分先赋 0
        HT[i].weight = 0;
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
    }
    
    for (i = n + 1; i <= m; ++i)
    {
        Select(HT, i - 1, s1, m2); // 在 HT 中挑选 parent=0, 而且权重最小的两个节点
        HT[s1].parent = i;
        HT[m2].parent = i;
        HT[i].lchild = s1;
        HT[i].rchild = m2;
        HT[i].weight = HT[s1].weight + HT[m2].weight;
    }
    
    HC = (HuffmanCode)malloc((n + 1) * sizeof(char*));
    char* cd = (char*)malloc(n * sizeof(char));
    cd[n - 1] = '\0'; // 结束符
    
    for (i = 1; i <= n; ++i)
    {
        // 从叶子节点到根节点,逆向求编码
        int start = n - 1;
        
        for (int c = i, f = HT[i].parent; f != 0; c = f, f = HT[f].parent)
        {
            if (HT[f].lchild == c)
                cd[--start] = '0'; // 左 0 右 1
            else
                cd[--start] = '1';
        }
        
        HC[i] = (char*)malloc((n - start) * sizeof(char));
        strcpy(HC[i], &cd[start]);
    }
    
    free(cd);
}

<5>Select 函数:

Status Select(HuffmanTree HT, int total, int &s1, int &m2)
{
    int min;
    
    // 得到最小权重s1
    for (int i = 1; i <= total; i++)
    {
        if (HT[i].parent == 0) 
        { 
            min = HT[i].weight; 
            s1 = i; 
        }
        else 
        {
            continue;
        }
        
        i = s1 + 1;
        
        while (i <= total)
        {
            if (min > HT[i].weight && HT[i].parent == 0) 
            { 
                min = HT[i].weight; 
                s1 = i; 
            }
            i++;
        }
        break;
    }
    
    // 得到次小权重m2
    for (int i = 1; i <= total; i++)
    {
        if (HT[i].parent == 0) 
        { 
            if (i == s1) 
            {
                continue; 
            }
            min = HT[i].weight; 
            m2 = i; 
        }
        else 
        {
            continue;
        }
        
        i = m2 + 1;
        
        while (i <= total)
        {
            if (min > HT[i].weight && HT[i].parent == 0)
            {
                if (i == s1) 
                { 
                    i++; 
                    continue; 
                }
                min = HT[i].weight;
                m2 = i;
            }
            i++;
        }
        break;
    }
    
    return OK;
}

<6> 输出每个字符的 Huffman 编码、将编码写入 b 中、将译码写入 c 中:

void ReadHuffmanCode(HuffmanTree HT, HuffmanCode HC, int* w, int n) {
    char Huffman[53][50];
    int i, j = 1, cnt = 0;
    
    for (i = 0; i < 26; ++i) {
        if (w[i] != 0) {
            printf("%c: %s ", 'a' + i, HC[j]);//由于 HC 存放的是出现过的字母种类编码,
            //而对于后续译码操作无法进行,需要 copy 到另一个二维数组,下标含义默认和w数组相同
            strcpy(Huffman[i], HC[j]);
            j++;
            cnt++;
            if (cnt == 4)
                printf("\n");
        }
    }

    
    for (i = 26; i < 52; ++i) {
        if (w[i] != 0) {
            printf("%c: %s ", 'A' + i - 26, HC[j]);
            strcpy(Huffman[i], HC[j]);//后续是将编码写入 b 文件的操作
            j++;
            cnt++;
            if (cnt == 4)
                printf("\n");
        }
    }

    strcpy(Huffman[52], HC[j]); 
    
    FILE* fp2 = fopen("C:\\Users\\LENOVO\\Desktop\\a.txt", "r");
    FILE* fp1 = fopen("C:\\Users\\LENOVO\\Desktop\\b.txt", "w");
    char ch;

    while ((ch = fgetc(fp2)) != EOF) {
        i = 0; j = 0;
        if (ch == ' ') {
            fputs(Huffman[52], fp1);
            continue;
        } else if ((int)ch >= 97 && (int)ch <= 122) {
            while (i < 26) {
                if (ch == 'a' + i) {
                    fputs(Huffman[i], fp1);
                    break;
                }
                i++;
            }
        } else if ((int)ch >= 65 && (int)ch <= 90) {
            i = 26;
            while (i < 52) {
                if (ch == 'A' + j) {
                    fputs(Huffman[i], fp1);
                    break;
                }
                i++;
                j++;
            }
        }
    }

    fclose(fp1);
    fclose(fp2);
	//后续是将译码写入 c 文件的操作
    FILE* fp3 = fopen("C:\\Users\\LENOVO\\Desktop\\b.txt", "r");
    FILE* fp4 = fopen("C:\\Users\\LENOVO\\Desktop\\c.txt", "w");

    int m = 2 * n - 1;
    char dh, eh[53];//eh 数组用来记录一个字符的哈夫曼编码
    int t = m, k = 0;
    j = 0;

    while ((dh = fgetc(fp3)) != EOF) {
    label:
        if (dh == '0') {
            t = HT[t].lchild;
            if (t != 0) {
                eh[k] = dh;
                k++;
            }
        } else if (dh == '1') {
            t = HT[t].rchild;
            if (t != 0) {
                eh[k] = dh;
                k++;
            }
        }

        if (t == 0) {
            for (i = 0; i < 53; i++) {
                for (j = 0;; j++) {
                    if (eh[j] == Huffman[i][j]) {//将此 eh 与标准的事先存放好的编码比对
                        if (j + 1 == k)
                            break;
                        continue;
                    } else
                        break;
                }
                if (eh[j] == Huffman[i][j])
                    break;//循环结束,找到 eh 字符串在 Huffman 数组中所在行 i
            }

            if (i >= 0 && i <= 25)
                fputc('a' + i, fp4);
            else if (i >= 26 && i <= 51)
                fputc('A' + i - 26, fp4);
            else if (i == 52)
                fputc(' ', fp4);

            t = m;
            k = 0;
            goto label;
        }
    }

    fclose(fp3);
    fclose(fp4);
}

六、运行示例

输入示例:Hello world
在这里插入图片描述

七、实验代码

//FileName:Huffman.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define OK 1
#define ERROR 0
typedef int Status;
typedef char** HuffmanCode;
typedef struct
{
	int weight;
	int parent;
	int lchild;
	int rchild;
}HTNode, *HuffmanTree;

Status Puta()
{
	int i = 0, j = 0;
	char ch[6][1000];
	for (i = 0; i < 6; i++)
	{
		fgets(ch[i], sizeof(ch[i]), stdin);
	}
	FILE* fp = fopen("C:\\Users\\LENOVO\\Desktop\\a.txt", "w");
	while (j < i)
	{
		fputs(ch[j], fp);
		j++;
	}
	fclose(fp);
	return OK;
}
Status GetWeight(int *w)
{
	FILE* fp = fopen("C:\\Users\\LENOVO\\Desktop\\a.txt", "r");
	if (fp == NULL)
	{
		printf("OPEN error!");
		return ERROR;
	}
	char ch; int i, j;
	while ((ch = fgetc(fp)) != EOF)
	{
		i = 0; j = 0;
		if (ch == '\0');
		else if (ch == ' ') { w[52]++; continue; }
		else if ((int)ch >= 97 && (int)ch <= 122)
		{
			while (i < 26)
			{
				if (ch == 'a' + i) { w[i]++; break; }
				i++;
			}
		}
		else if ((int)ch >= 65 && (int)ch <= 90)
		{
			i = 26;
			while (i < 52)
			{
				if (ch == 'A' + j) { w[i]++; break; }
				i++;
				j++;
			}
		}
	}
	i = 0; int n = 0;
	for (i = 0; i < 52; i++)
	{
		if (w[i] != 0)n++;
	}
	fclose(fp);
	return n;
}

Status ReadW(int* w)
{
	int sum = 0, i = 0, j = 0, cnt = 0;
	while (i < 52)
	{
		sum = sum + w[i];
		i++;
	}
	i = 0;
	printf("This pasage has %d words\n", sum);
	while (i < 26)
	{
		printf("%c: %d   ", 'a' + i, w[i]);
		cnt++;
		i++;
		if (cnt == 4) { cnt = 0; printf("\n"); }
	}
	cnt = 0;
	printf("\n");
	while (i < 52)
	{
		printf("%c: %d   ", 'A' + j, w[i]);
		i++;
		j++;
		cnt++;
		if (cnt == 4) { cnt = 0; printf("\n"); }
	}
	return OK;
}
Status Select(HuffmanTree HT, int total, int &s1, int &m2)
{
	int min;
	for (int i = 1; i <= total; i++)
	{
		if (HT[i].parent == 0) { min = HT[i].weight; s1 = i; }
		else continue;
		i = s1 + 1;
		while (i <= total)
		{
			if (min > HT[i].weight&& HT[i].parent == 0) { min = HT[i].weight; s1 = i; }
			i++;
		}
		break;
	}
	for (int i = 1; i <= total; i++)
	{
		if (HT[i].parent == 0) { if (i == s1)continue; min = HT[i].weight; m2 = i; }
		else continue;
		i = m2 + 1;
		while (i <= total)
		{
			if (min > HT[i].weight&& HT[i].parent == 0)
			{
				if (i == s1) { i++; continue; }
				min = HT[i].weight; 
				m2 = i;
			}
			i++;
		}
		break;
	}
	return OK;
}

void HuffmanCoding(HuffmanTree& HT, HuffmanCode& HC, int* w, int n)
{
	HuffmanTree p; int i, s1, m2, j=0;
	if (n <= 1)return;
	int m = 2 * n - 1;
	HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));
	for (i = 1; i <= n; ++i)
	{
		while (w[j] == 0)
		{
			j++;
		}
		HT[i].weight = w[j];
		HT[i].parent = 0;
		HT[i].lchild = 0;
		HT[i].rchild = 0;
		j++;
	}
	for (; i <= m; ++i)
	{
		HT[i].weight = 0;
		HT[i].parent = 0;
		HT[i].lchild = 0;
		HT[i].rchild = 0;
	}
	for (i = n + 1; i <= m; ++i)
	{
		Select(HT, i - 1, s1, m2);
		HT[s1].parent = i;
		HT[m2].parent = i;
		HT[i].lchild = s1;
		HT[i].rchild = m2;
		HT[i].weight = HT[s1].weight + HT[m2].weight;
	}
	HC = (HuffmanCode)malloc((n + 1) * sizeof(char*));
	char* cd = (char*)malloc(n * sizeof(char));
	cd[n - 1] = '\0';
	for (i = 1; i <= n; ++i)
	{
		int start = n - 1;
		for (int c = i, f = HT[i].parent; f != 0; c = f, f = HT[f].parent)
		{
			if (HT[f].lchild == c)cd[--start] ='0';
			else cd[--start] = '1';
		}
		HC[i] = (char*)malloc((n - start) * sizeof(char));
		strcpy(HC[i], &cd[start]);
	}
	free(cd);
}

void ReadHuffmanCode(HuffmanTree HT,HuffmanCode HC, int* w,int n)
{
	char Huffman[53][50];
	int i, j = 1, cnt = 0;
	for (i = 0; i < 26; ++i)
	{
		if (w[i] != 0)
		{
			printf("%c: %s   ", 'a' + i, HC[j]);
			strcpy(Huffman[i], HC[j]);
			j++;
			cnt++;
			if (cnt == 4)printf("\n");
		}
	}
	for (i; i < 52; ++i)
	{
		if (w[i] != 0)
		{
			printf("%c: %s   ", 'A' + i - 26, HC[j]);
			strcpy(Huffman[i], HC[j]);
			j++;
			cnt++;
			if (cnt == 4)printf("\n");
		}
	}
	strcpy(Huffman[i], HC[j]);
	FILE* fp2 = fopen("C:\\Users\\LENOVO\\Desktop\\a.txt", "r");
	FILE* fp1 = fopen("C:\\Users\\LENOVO\\Desktop\\b.txt", "w");
	char ch;
	while ((ch = fgetc(fp2)) != EOF)
	{
		i = 0; j = 0;
		if (ch == ' ')
		{
			fputs(Huffman[52], fp1);
			continue;
		}
		else if ((int)ch >= 97 && (int)ch <= 122)
		{
			while (i < 26)
			{
				if (ch == 'a' + i) { fputs(Huffman[i], fp1);  break; }
				i++;
			}
		}
		else if ((int)ch >= 65 && (int)ch <= 90)
		{
			i = 26;
			while (i < 52)
			{
				if (ch == 'A' + j) { fputs(Huffman[i], fp1); break; }
				i++;
				j++;
			}
		}

	}
	fclose(fp1); fclose(fp2);
	FILE* fp3 = fopen("C:\\Users\\LENOVO\\Desktop\\b.txt", "r");
	FILE* fp4 = fopen("C:\\Users\\LENOVO\\Desktop\\c.txt", "w");
	int m = 2 * n - 1; char dh, eh[53]; int t = m, k = 0; j = 0;
	while ((dh = fgetc(fp3)) != EOF)
	{
		lable:
		if (dh == '0')
		{
			t = HT[t].lchild;
			if (t != 0)
			{
				eh[k] = dh;
				k++;
			}
		}
		else if (dh == '1')
		{
			t = HT[t].rchild;
			if (t != 0)
			{
				eh[k] = dh;
				k++;
			}
		}
		if (t == 0)
		{
			for (i = 0; i < 53; i++)
			{
				for (j = 0;; j++)
				{
					if (eh[j] == Huffman[i][j]) { if (j + 1 == k)break; continue; }
					else break;
				}
				if (eh[j] == Huffman[i][j])break;
			}
			if (i >= 0 && i <= 25)fputc('a' + i, fp4);
			else if (i >= 26 && i <= 51)fputc('A' + i - 26, fp4);
			else if (i == 52)fputc(' ', fp4);
			t = m;k = 0;
			goto lable;
		}
	}
	fclose(fp3); fclose(fp4);
}

int main()
{
	int kind; HuffmanTree HT; HuffmanCode HC;
	int w[55] = { 0 };
	Puta();
	kind = GetWeight(w) + 1;
	ReadW(w);
	HuffmanCoding(HT, HC, w, kind);
	printf("\n\nEvery Word's HuffmanCode: \n");
	ReadHuffmanCode(HT, HC, w, kind);
	return 0;
}

八、算法测试结果

1. 输入:Hello world (输入时最后需要空格一下)-------当时没解决的bug,现在我也懒得看了,反正知道哈夫曼树怎么来的就行了,,,(doge
在这里插入图片描述
2. a文件:
在这里插入图片描述
3. b文件:
在这里插入图片描述
4. c文件:
在这里插入图片描述

九、分析与总结

9.1 算法复杂度分析及优、缺点分析

  1. 由于在 ReadHuffmanCode 有双重循环,对于 n 个字符的输入,每个字符都会 有一次双重循环,所以时间复杂度O(n3)
  2. 此代码可以解决较长字符的运算以及输出,代码容易理解;但是略有繁冗,而且在 ReadHuffmanCode 函数中,在译码时,由于没有更好的办法解决文件指针后移情况,使用了 goto 语句,略有破坏代码的可读性;
  3. 并且在输入英文短文时,不能输入标点符号,而且在输入结束后,需要额外输入一个无用字符来弥补译码时的文件指针问题,以上均是代码的不足。

9.2 实验总结

  1. 熟悉了 Huffman 树的创建,以及对文件操作的理解;
  2. 在解决输入多行数据时遇到了困难,最后采用了 fgets 输入;
  3. Huffman 树,只有度为 0 或者 2 的节点,所以不存在某个字符的编码是另一个字符的前缀,这是本代码运行的前提;但在译码时使用了 goto 语句,使可读性变差;
  4. 可以在 Huffman 结构体中新增一个 char data 成员,用来储存字母,以减少遍历次数。

ps: 好累啊,,,赶紧溜~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值