哈夫曼编/译码器【NOJ实验第7题】

前言

  在造哈夫曼树的时候用了不少时间,因为之前写的哈夫曼树是错的(没错,不过上一篇文章已经重投了)

  这道题思路很简单,但实现起来感觉还是有点麻烦。。。

题目描述

思路详解

先来看看解决方案的结构

struct HuffmanTreeNode
{
    bool hasChecked; // 检查该结点是否在森林中,true表示不在
    int weight;      // 权值
    int LChild;      // 左孩子下标
    int RChild;      // 右孩子下标
    int parent;      // 双亲下标
};
class HuffmanTree
{
private:
    HuffmanTreeNode *tree; // 哈夫曼树结点数组
    int n;                 // 结点数组的长度

public:
    void init(int weight[], int n, char character[]);                        // 创建并初始化一颗哈夫曼树
    void search(int max, int &s1, int &s2);                                  // 寻找森林里权值最小的两棵树
    void sort(int weight[], char character[]);                               // 对输入的权值序列以及其对应的字母顺序进行排序
    void createCode(char **result, int *weight);                             // 创造字母对应的哈夫曼编码
    void printCode(char *input, char **result, char *character, char *code); // 输出字母转换的哈夫曼编码,并将其保存于code中
    void paresCodeAndPrint(char *code, char **result, char *character);      // 将code中的哈夫曼编码转化为字母并打印输出
};

 题目要求我们输入长度$n$,$n$个字符,$n$个权值以及原始码序列,但我们首先需要创建哈夫曼树的一个实例,因此在main函数中

int main(){
    HuffmanTree aTree;               // 实例化的树
    int n;                           // 输入长度n
    int weight[100] = {0};           // 权值序列
    char character[100] = {0};       // 对应的字母序列
    char input[100] = {0};           // 输入的原始字符串
    char **result = new char *[100]; //每个字母对应的哈夫曼编码数组
    char code[100] = {'\0'};         // 转换成的哈夫曼编码
    for (int i = 0; i < 100; i++)
    {
        result[i] = new char[100];
    }

    std::cin >> n;
    for (int i = 0; i < n; i++)
    {
        std::cin >> character[i];
    }
    for (int i = 0; i < n; i++)
    {
        std::cin >> weight[i];
    }
    std::cin >> input;

 这下输入完成了,那么接下来我们应该创建哈夫曼树,也就是初始化。

void HuffmanTree::init(int weight[], int n, char character[])
{
    this->n = n;
    tree = new HuffmanTreeNode[2 * n];
    sort(weight, character);
    //初始化前1~n个
    for (int i = 1; i <= n; i++)
    {
        tree[i] = {false, weight[i - 1], 0, 0, 0};
    }
    //初始化后n + 1~2n - 1个
    for (int i = n + 1; i <= 2 * n - 1; i++)
    {
        tree[i] = {false, 100, 0, 0, 0};
    }
    int s1, s2;
    for (int i = n + 1; i <= 2 * n - 1; i++)
    {
        search(i - 1, s1, s2);
        // 找出权值最小的两颗子树
        // 由于需要让权值小的在左子树,因此我们让权值s1 < s2
        tree[i].weight = tree[s1].weight + tree[s2].weight;
        tree[i].LChild = s1;
        tree[i].RChild = s2;

        tree[s1].parent = i;
        tree[s2].parent = i;
    }
}

而其中的search函数实现如下

void HuffmanTree::search(int max, int &s1, int &s2)
{
    int i, min = 999, k = 1;
    for (i = 1; i <= max; i++)
    {
        if (tree[i].hasChecked) // 如果该树不在森林中则跳过
        {
            continue;
        }

        if (tree[i].weight < min)
        {
            min = tree[i].weight;
            k = i;
        }
    }
    s1 = k;
    tree[s1].hasChecked = true; // 将找到的最小权值的树移出森林再寻找最小的树

    k = 1;
    min = 999;
    for (i = 1; i <= max; i++)
    {
        if (tree[i].hasChecked)
        {
            continue;
        }

        if (tree[i].weight < min)
        {
            min = tree[i].weight;
            k = i;
        }
    }
    s2 = k;
    tree[k].hasChecked = true;
}

 其中为了保证有序还对输入权值序列进行了排序(可能非必要)

void HuffmanTree::sort(int *weight, char character[])
{ // 选择排序
    int i, j, k;
    for (i = 0; i < n - 1; i++)
    {
        int min = weight[i];
        k = i;
        for (j = i + 1; j < n; j++)
        {
            if (weight[j] < min)
            {
                min = weight[j];
                k = j;
            }
        }
        if (k != i)
        {
            // 字母数组与权值序列同时进行排序以保持对应关系
            int t = weight[k];
            weight[k] = weight[i];
            weight[i] = t;

            char m = character[k];
            character[k] = character[i];
            character[i] = m;
        }
    }
}

 创建完哈夫曼树后,即是根据哈夫曼树来创造哈夫曼编码

void HuffmanTree::createCode(char **result, int *weight)
{
    // 自树的叶子结点(即前1~n个结点)向上进行编码
    for (int i = 1; i <= n; i++)
    {
        int k = 0;
        for (int j = 1; j < 2 * n; j++) // 寻找在树节点数组里对应的权值
        {
            // 若不是叶子结点
            if (tree[j].LChild || tree[j].RChild)
            {
                continue;
            }
            else
            {
                if (tree[j].weight == *weight)
                {
                    k = j;
                    weight++;
                    break;
                }
            }
        }
        char *temp = *result; // 记录字符串开始位置
        if (k == 0)
            return;
        while (tree[k].parent != 0) // 找到之后开始从下网上进行编码
        {
            if (tree[tree[k].parent].LChild == k)
            {
                **result = '0';
                (*result)++;
            }
            else if (tree[tree[k].parent].RChild == k)
            {
                **result = '1';
                (*result)++;
            }
            k = tree[k].parent;
        }
        **result = '\0';  // 补全字符串
        (*result) = temp; // 找回原字符串位置
        // 对数组进行翻转
        for (int m = 0, j = (strlen((*result)) - 1); m < j; m++, j--)
        {
            char temp = (*result)[m];
            (*result)[m] = (*result)[j];
            (*result)[j] = temp;
        }
        result++;
    }
}

现在每一个字母对应的哈夫曼编码已经得到了,保存于result中,那么下面就是根据编码输出了,这里为了方便下一步操作,还将输出的哈夫曼编码保存在了code中

void HuffmanTree::printCode(char *input, char **result, char *character, char *code)
{
    while (*input != '\0')
    {
        // 寻找result中对应的结果
        int i;
        for (i = 0; i < n; i++)
        {
            if (*input == character[i])
            {
                break;
            }
        }
        std::cout << result[i];
        strcat(code, result[i]); // 构造code
        input++;
    }
}

最后一步便是根据哈夫曼编码输出原始字符串了

void HuffmanTree::paresCodeAndPrint(char *code, char **result, char *character)
{
    while (*code != '\0')
    {
        for (int i = 0; i < n; i++)
        {
            if (strncmp(code, result[i], strlen(result[i])) == 0)
            {
                std::cout << character[i];
                code += strlen(result[i]);
                break;
            }
        }
    }
}

到此这道题结束

完整代码实现

/**
 * @file 7.cpp
 * @author zh(帝皇の惊)(RickSchanze)
 * @brief 哈夫曼编/译码器
 * @date 2022-04-19
 */
#include <iostream>
#include <cstring>

struct HuffmanTreeNode
{
    bool hasChecked; // 检查该结点是否在森林中,true表示不在
    int weight;      // 权值
    int LChild;      // 左孩子下标
    int RChild;      // 右孩子下标
    int parent;      // 双亲下标
};

class HuffmanTree
{
private:
    HuffmanTreeNode *tree; // 哈夫曼树结点数组
    int n;                 // 结点数组的长度

public:
    void init(int weight[], int n, char character[]);                        // 创建并初始化一颗哈夫曼树
    void search(int max, int &s1, int &s2);                                  // 寻找森林里权值最小的两棵树
    void sort(int weight[], char character[]);                               // 对输入的权值序列以及其对应的字母顺序进行排序
    void createCode(char **result, int *weight);                             // 创造字母对应的哈夫曼编码
    void printCode(char *input, char **result, char *character, char *code); // 输出字母转换的哈夫曼编码,并将其保存于code中
    void paresCodeAndPrint(char *code, char **result, char *character);      // 将code中的哈夫曼编码转化为字母并打印输出
};

void HuffmanTree::init(int weight[], int n, char character[])
{
    this->n = n;
    tree = new HuffmanTreeNode[2 * n];
    sort(weight, character);
    //初始化前1~n个
    for (int i = 1; i <= n; i++)
    {
        tree[i] = {false, weight[i - 1], 0, 0, 0};
    }
    //初始化后n + 1~2n - 1个
    for (int i = n + 1; i <= 2 * n - 1; i++)
    {
        tree[i] = {false, 100, 0, 0, 0};
    }
    int s1, s2;
    for (int i = n + 1; i <= 2 * n - 1; i++)
    {
        search(i - 1, s1, s2);
        // 找出权值最小的两颗子树
        // 由于需要让权值小的在左子树,因此我们让权值s1 < s2
        tree[i].weight = tree[s1].weight + tree[s2].weight;
        tree[i].LChild = s1;
        tree[i].RChild = s2;

        tree[s1].parent = i;
        tree[s2].parent = i;
    }
}

void HuffmanTree::search(int max, int &s1, int &s2)
{
    int i, min = 999, k = 1;
    for (i = 1; i <= max; i++)
    {
        if (tree[i].hasChecked) // 如果该树不在森林中则跳过
        {
            continue;
        }

        if (tree[i].weight < min)
        {
            min = tree[i].weight;
            k = i;
        }
    }
    s1 = k;
    tree[s1].hasChecked = true; // 将找到的最小权值的树移出森林再寻找最小的树

    k = 1;
    min = 999;
    for (i = 1; i <= max; i++)
    {
        if (tree[i].hasChecked)
        {
            continue;
        }

        if (tree[i].weight < min)
        {
            min = tree[i].weight;
            k = i;
        }
    }
    s2 = k;
    tree[k].hasChecked = true;
}

void HuffmanTree::sort(int *weight, char character[])
{ // 选择排序
    int i, j, k;
    for (i = 0; i < n - 1; i++)
    {
        int min = weight[i];
        k = i;
        for (j = i + 1; j < n; j++)
        {
            if (weight[j] < min)
            {
                min = weight[j];
                k = j;
            }
        }
        if (k != i)
        {
            // 字母数组与权值序列同时进行排序以保持对应关系
            int t = weight[k];
            weight[k] = weight[i];
            weight[i] = t;

            char m = character[k];
            character[k] = character[i];
            character[i] = m;
        }
    }
}

void HuffmanTree::createCode(char **result, int *weight)
{
    // 自树的叶子结点(即前1~n个结点)向上进行编码
    for (int i = 1; i <= n; i++)
    {
        int k = 0;
        for (int j = 1; j < 2 * n; j++) // 寻找在树节点数组里对应的权值
        {
            // 若不是叶子结点
            if (tree[j].LChild || tree[j].RChild)
            {
                continue;
            }
            else
            {
                if (tree[j].weight == *weight)
                {
                    k = j;
                    weight++;
                    break;
                }
            }
        }
        char *temp = *result; // 记录字符串开始位置
        if (k == 0)
            return;
        while (tree[k].parent != 0) // 找到之后开始从下网上进行编码
        {
            if (tree[tree[k].parent].LChild == k)
            {
                **result = '0';
                (*result)++;
            }
            else if (tree[tree[k].parent].RChild == k)
            {
                **result = '1';
                (*result)++;
            }
            k = tree[k].parent;
        }
        **result = '\0';  // 补全字符串
        (*result) = temp; // 找回原字符串位置
        // 对数组进行翻转
        for (int m = 0, j = (strlen((*result)) - 1); m < j; m++, j--)
        {
            char temp = (*result)[m];
            (*result)[m] = (*result)[j];
            (*result)[j] = temp;
        }
        result++;
    }
}

void HuffmanTree::printCode(char *input, char **result, char *character, char *code)
{
    while (*input != '\0')
    {
        // 寻找result中对应的结果
        int i;
        for (i = 0; i < n; i++)
        {
            if (*input == character[i])
            {
                break;
            }
        }
        std::cout << result[i];
        strcat(code, result[i]); // 构造code
        input++;
    }
}

void HuffmanTree::paresCodeAndPrint(char *code, char **result, char *character)
{
    while (*code != '\0')
    {
        for (int i = 0; i < n; i++)
        {
            if (strncmp(code, result[i], strlen(result[i])) == 0)
            {
                std::cout << character[i];
                code += strlen(result[i]);
                break;
            }
        }
    }
}

int main()
{
    HuffmanTree aTree;               // 实例化的树
    int n;                           // 输入长度n
    int weight[100] = {0};           // 权值序列
    char character[100] = {0};       // 对应的字母序列
    char input[100] = {0};           // 输入的原始字符串
    char **result = new char *[100]; //每个字母对应的哈夫曼编码数组
    char code[100] = {'\0'};         // 转换成的哈夫曼编码
    for (int i = 0; i < 100; i++)
    {
        result[i] = new char[100];
    }

    std::cin >> n;
    for (int i = 0; i < n; i++)
    {
        std::cin >> character[i];
    }
    for (int i = 0; i < n; i++)
    {
        std::cin >> weight[i];
    }
    std::cin >> input;
    aTree.init(weight, n, character);
    aTree.createCode(result, weight);
    aTree.printCode(input, result, character, code);
    std::cout << std::endl;
    aTree.paresCodeAndPrint(code, result, character);
    return 0;
}

  

 

posted @ 2022-04-19 20:25  帝皇の惊  阅读(104)  评论(0)    收藏  举报