哈夫曼树以及哈夫曼编码的C++实现

利用哈夫曼编码进行信息通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站写一个哈夫曼编/译码系统。

测试数据:“THIS  PROGRAM  IS  MY  FAVORITE”

字符

空格

A

B

C

D

E

F

G

H

I

J

K

L

M

频度

186

64

13

22

32

103

21

15

47

57

1

5

32

20

字符

N

O

P

Q

R

S

T

U

V

W

X

Y

Z

频度

57

63

15

1

48

51

80

23

8

18

1

16

1

课设作业,记得没错也是蓝桥杯的比赛题目,既然写的差不多完整了就想着放出来吧。

差不多是在b站上看王卓老师把这个东西写完整的,很不错的视频。

哈夫曼树也即是最优二叉树,哈夫曼编码则是哈夫曼树的延申。

给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

明白这整个关系就可以很好的理解出哈夫曼编码。

哈夫曼编码:从根出发,到达所在的结点,其路径就是哈夫曼编码。

每次经过左子树生成0,每次经过右节点生成1。

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#pragma warning(disable:4996)
#pragma warning(disable:6283)
#pragma warning(disable:6001)
#pragma warning(disable:4018)
#define MAX 1000
using namespace std;
//哈夫曼树的存储表示
typedef char ElemType;
typedef struct
{
    ElemType data;              //结点存的数据
    int weight;                 //结点的权值
    int parent, lchild, rchild;   //结点的双亲、左孩子、右孩子的下标
} HTNode, * HuffmanTree;          //动态分配数组存储哈夫曼树
typedef char** HuffmanCode;//动态分配数组存储哈夫曼编码表--根据哈夫曼树求哈夫曼编码
void CreateHuffmanTree(HuffmanTree& HT, int n);
void CreateHuffmanCode(HuffmanTree HT, HuffmanCode& HC, int& n);
void HaffumanDecode(string s, HuffmanTree HT, int n);
void Select(HuffmanTree HT, int n, int& s1, int& s2);
/*---文件操作---*/
ofstream outfile("output.txt");//写入文件
ifstream readfile("output.txt");//读取文件
ofstream outCoding("Coding.txt");//写入文件
ifstream readCoding("Coding.txt");//读取文件
ofstream otable("table.txt");
ifstream rtable("table.txt");
/*有点文件操作上的bug
*/
/*哈夫曼编码器思路
哈夫曼树->哈夫曼编码器
*/
//例样输入
//THIS--PROGRAM--IS--MY--FAVORITE
//80 47 57 51 186 186 15 48 63 15 48 64 20 186 186 57 51 186 186 20 16 186 186 21 64 8 63 48 57 80 103
//菜单
void Menu()
{
    cout << "**********操作循序:(1.输入哈夫曼树)->(2.初始化哈夫曼树)->(3.读取字符数据测试)*****************" << endl;
    cout << "**********操作循序:(4.创建编码表)->(5.读取编码数据测试)->(6.输出编码表)***********************" << endl;
    cout << "**********操作循序:(7.读取表数据测试)->(8.手动编码-转字符)->(9.手动译码-转编码)***************" << endl;
    cout << "**********操作循序:(10.从文件中抽取字符进行编码-转字符)->(11.从文件中抽取字符进行译码-转编码)*" << endl;
    cout << "********************0.*************************************" << endl;
    cout << "********************1.输入HuffmanTree的参数***************" << endl;
    cout << "********************2.检测是否初始化HuffmanTree参数*******" << endl;
    cout << "********************3.创建编码表**************************" << endl;
    cout << "********************4.输出编码表**************************" << endl;
    cout << "********************5.手动编码-转字符*********************" << endl;
    cout << "********************6.手动译码-转编码****************************" << endl;
    cout << "********************7.退出********************************" << endl;
    cout << "********************8.从文件中抽取字符进行编码-转字符******" << endl;
    cout << "********************9.从文件中抽取字符进行译码-转编码*************" << endl;
    cout << "********************10.清空文件内容************************" << endl;
    cout << "********************11.读取字符数据测试********************" << endl;
    cout << "********************12.读取编码数据测试********************" << endl;
    cout << "********************13.读取表数据测试**********************" << endl;
    cout << "***********************************************************" << endl;
    cout << "请输入选择:";
}
//编码菜单
void Codingtable()
{
    cout << "******TIPS:本次课题要求输入:THIS--PROGRAM--IS--MY--FAVORITE" << endl;
    cout << "********题目要求编码表********" << endl;
    cout << "空格-186 ,(请用-代替空格)" << endl;
    cout << "A-64 B-13 C-22 D-32 E-103  " << endl;
    cout << "F-21 G-15 H-47 I-57 J-1" << endl;
    cout << "K-5  L-32 M-20 N-57 O-63 " << endl;
    cout << "P-15 Q-1  R-18 S-51 T-80 " << endl;
    cout << "U-23 V-8  W-18 X-1  Y-16 Z-1" << endl;
};

int main()
{
    HuffmanTree HT = NULL;
    HuffmanCode HC = NULL;
    string number;
    string shuru;
    int  n;
    char c;
    //c用来记录输入
    while (1)
    {
        Menu();
        flage:
        string choose;
        cin >> choose;
        int len = choose.length();
        int trans=0;
        if (len == 1)
        {
            trans = choose[0];
        }
        else if (len == 2)
        {
            for (int i = 0;i < 2;i++)
            {
                int tmp = choose[i];
                trans += tmp;
            }
        }
        else
        {
            cout << "不是可以操作的个位数请重新输入" << endl;
            goto flage;
        }
        switch (trans)
        {
        case 48:
        {
            cout << "暂时此功能";
            break;
        }
        case 49:
            Codingtable();
            cout << "请输入结点的个数:";
               cin >> n;
               CreateHuffmanTree(HT, n);
            break;
        case 50:
            if (n > 1)
            {
                cout << "HuffmanTree初始化完成!" << endl;
            }
            else
            {
                cout << "您给我老老实实把哈夫曼树给建了,别在咖啡厅里点蛋炒饭" << endl;
            }
            break;
        case 51:
            if (HT == NULL)
            {
                cout << "请先创建HuffmanTree!" << endl;
            }
            else
            {
                CreateHuffmanCode(HT, HC, n);
                cout << "HuffmanCode创建完成!" << endl;
            }
            break;
        case 52://打印编码表
         
            if (HC == NULL)
            {
                cout << "请先创建HuffmanCode!" << endl;
            }
            else
            {
                cout << "输出编码表如下:" << endl;
                for (int i = 1; i <= n; i++)
                {
                    otable << "字符:" << HT[i].data << " 编码:" << HC[i] << endl;
                    cout << "字符:" << HT[i].data << " 编码:" << HC[i] << endl;

                }
            
            }
            break;
        case 53://编码
        {
            if (HC == NULL)
            {
                cout << "请先创建HuffmanCode!" << endl;
            }
            else
            {

                c = cin.get();
                cout << "请输入需要转成字符的哈夫曼编码:" << endl;
                string str, code;
                getline(cin, code);
                cout << "转成字符为:" << endl;
                HaffumanDecode(code, HT, n);
            }
            break;
        }
        case 54://译码
        {
            if (HC == NULL)
            {
                cout << "请先创建HuffmanCode!" << endl;
            }
            else
            {
                //输入字符实现转码
                c = cin.get();
                cout << "请输入需要转成编码的字符:" << endl;
                string s;
                getline(cin, s);
                for (int i = 0; i < s.length(); i++)
                {
                    for (int j = 1; j <= n; j++)
                    {
                        if (HT[j].data == s[i])
                        {
                            cout << HC[j];
                        }
                    }
                }
                break;
            }
        }
        case 55:
        {
            system("pause");
            exit(0);
        }
        case 56://文件编码
        {
            string buffer;
            string str,code;
            if (!readCoding.is_open())
            {
                cout << "Error opening file"; exit(1);
            }
            cout << "编码为:"<> YorN;
            if (YorN == 'Y')
            {
                outfile.is_open();
                outCoding.is_open();
                outfile.close();
                outCoding.close();
                //只写的方式打开相当于清空
                cout << "清空目录咯~";
            }
            else if (YorN == 'N')
            {
                cout << "取消清空" << endl;
                exit(1);
            }
            else
            {
                cout << "请输入正确的字符";
                goto choice;
            }
            break;
        }
        case 98://测试字符文件
        {
            string buffer;
            if (!readfile.is_open())
            {
                cout << "Error opening file"; exit(1);
            }
            while (getline(readfile,buffer))
            {
                cout << buffer;
            }
            break;
        }
        case 99://测试编码文件
        {
            char buffer2[MAX];
            if (!readCoding.is_open())
            {
                cout << "Error opening file"; exit(1);
            }
            while (!readCoding.eof())
            {
                readCoding.getline(buffer2, 1000);
                cout << buffer2 << endl;
            }
            break;
        }
        case 100:
        {
            string buffer3;
            if (!rtable.is_open())
            {
                cout << "Error opening file"; exit(1);
            }
            while (getline(rtable,buffer3))
            {     
                cout << buffer3 << endl;
            }
            break;
        }
        default:
        {
            cout << "能不能老老实实输入数据不要花里胡巧的?" << endl;
            break;
        }
        }
        cout << endl;
    }
    return 0;
}
/*----关键函数----*/
/*------构建哈夫曼树------*/
/*----Select()----*/
void Select(HuffmanTree HT, int n, int& s1, int& s2)
{
    int i = 1;//开始初始化wetmin1,wetmin2,使用一个i往后找,HT[i].parent==0的结点
    int wetmin1 = 100000;
    int wetmin2 = 100000;//定义一个巨大的wetmin1和wetmin2

    //初始化wetmin1,wetmin2,将小的赋值给wetmin1

    for (i; i <= n; i++)
    {
        if (HT[i].parent == 0 && HT[i].weight < wetmin1)
        {
            wetmin2 = wetmin1;
            wetmin1 = HT[i].weight;
            s2 = s1;
            s1 = i;
        }
        else if (HT[i].parent == 0 && HT[i].weight < wetmin2)
        {
            wetmin2 = HT[i].weight;
            s2 = i;
        }
    }
}
//Select用于找出最小的两个结点
void CreateHuffmanTree(HuffmanTree& HT, int n)
{
    outfile.is_open();
    //构造哈夫曼树HT
    if (n <= 1)
    {
        cout << "HuffmanTree节点数输入错误!" << endl;
        return;
    }
    int m = 2 * n - 1;               //数组共2n-1个元素
    int s1 = 1, s2 = 1;
    HT = new HTNode[m + 1];         //0号单元未用,HT[m]表示根结点
    for (int i = 1; i <= m; ++i)     //将1~m号单元中的双亲、左孩子、右孩子的下标都初始化为0
    {
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
    }
    /*-----------------------*/
input:
    string shuru;
    cout << "请输入-Advance/Keyboard" << endl;
    cin >> shuru;
    /*-------------------预先输入-----------------------*/
    if (shuru == "Advance")//预输入-方便做题用
    {
        string AIF1 = "THIS--PROGRAM--IS--MY--FAVORITE";
        int longer1 = AIF1.length();
        if (longer1 != n)
        {
            cout << "结点个数与预输入的结点个数不符合,即将结束程序" << endl;
            exit(0);
        }
        for (int i = 1, j = 0; i <= longer1; ++i, ++j)     //输入前n个单元中叶子结点的数据(空格用-代替)
        {
            HT[i].data = AIF1[j];

        }
        /*-----------------------*/
        int AIF2[MAX] = { 80,47,57,51,186,186,15,48,63,15,48,64,20,186,186,57,51,186,186,20,16,186,186,21,64,8,63,48,57,80,103 };
        int longer2 = 0;
        while (AIF2[longer2] != NULL)
        {
            longer2++;//求数组长度用
        }
        for (int i = 1, j = 0; i <= longer2; ++i, ++j)     //输入前n个单元中叶子结点的权值(空格用-代替)
        {
            HT[i].weight = AIF2[j];
        }

    }
    /*-------------------键盘输入-----------------------*/
    else if (shuru == "Keyboard")//键盘输入-会帮你将数据存入文件里头
    {
        cout << "请输入" << n << "个叶子结点的数据(字符):" << endl;

        for (int i = 1; i <= n; ++i)     //输入前n个单元中叶子结点的数据(空格用-代替)
        {
            cin >> HT[i].data;
            outfile << HT[i].data;
        }
        outfile << endl;
        /*------*/
        cout << "请输入第" << n << "个叶子结点的权值:" << endl;
        for (int i = 1; i <= n; ++i)     //输入前n个单元中叶子结点的权值(空格用-代替)
        {
            cin >> HT[i].weight;
            outfile << HT[i].weight << ' ';//一个整形 一个空格
        }
    }
    else
    {
        cout << "输入有误,请重新输入";
        goto input;
    }
    /*------------------------------------------------------------------------------------------------*/
    /*--------------------------初始化工作结束,下面开始创建完整哈夫曼树---------------------------*/
    for (int i = n + 1; i <= m; ++i)//合并产生n-1个结点-构造哈夫曼树
    {
        Select(HT, i - 1, s1, s2);      //在HT[k](1<=k<=i-1)中选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号s1和s2
        HT[s1].parent = i;
        HT[s2].parent = i;            //得到新结点i,从F中删除s1,s2,将s1和s2的双亲域由0改为i
        HT[i].lchild = s1;
        HT[i].rchild = s2;            //s1,s2分别作为i的左右孩子
        HT[i].weight = HT[s1].weight + HT[s2].weight;   //i的权值为左右孩子权值之和
    }
    outfile.close();
}
/*----------------------------*/
/*----------------------以上已经完成哈夫曼树的创建-----------------------------*/
/*----------------------以下是完成哈夫曼编码的创建-----------------------------*/
//哈夫曼编码表的的存储表示
//哈夫曼编码既是由根结点到所求结点的路径所得出的,路径在左边就得出0在右边就得出1
void CreateHuffmanCode(HuffmanTree HT, HuffmanCode& HC, int& n)
{
    outCoding.is_open();
    //从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
    HC = new char* [n + 1];         //分配存储n个字符编码的编码表动态空间
    char* cd = new char[n];         //分配临时存放每个字符编码的动态数组空间
    cd[n - 1] = '\0';               //(末尾)编码结束符
    for (int i = 1; i <= n; i++) //逐个字符求哈夫曼编码
    {
        int start = n - 1;      //start开始时指向最后,即编码结束符位置
        int c = i, f = HT[i].parent;//f指向结点c的双亲结点
        while (f != 0)         //从叶子结点开始向上回溯,直到根结点
        {
            --start;
            if (HT[f].lchild == c) 
                cd[start] = '0';//结点c是f的左孩子,则生成代码0
            else 
                cd[start] = '1';               //结点c是f的右孩子,则生成代码1
            c = f;
            f = HT[f].parent;               //继续向上回溯
        }                    //求出第i个字符的编码
        HC[i] = new char[n - start];              //为第i个字符编码分配空间
        strcpy(HC[i], &cd[start]);             //将求得的编码从临时空间cd复制到HC的当前行中
        outCoding << HC[i];
    }
    //释放临时空间
    outCoding.close();
}
//解码
void HaffumanDecode(string s, HuffmanTree HT, int n)
{
    int p = 2 * n - 1;
    int lens = s.size();
    for (int i = 0; i < lens; i++)
    {
        //对编码一位一位进行查找,找到对应的叶节点为止
        if (s[i] == '0')//如果碰到0,进行一次输出
        {
            if (HT[p].lchild != 0)
            {
                p = HT[p].lchild;//左边进行迭代
            }
        }
        else if (s[i] == '1')//如果碰到1,进行输出
        {
            if (HT[p].rchild != 0)
            {
                p = HT[p].rchild;//右边进行迭代
            }
        }
        if (HT[p].rchild == 0 && HT[p].lchild == 0)
        {
            //如果是叶子结点,输出,然后让p重新指向数顶,开始新一轮寻找
            cout << HT[p].data;
            p = 2 * n - 1;
        }

    }
    cout << endl;
}

你可能感兴趣的:(c++,数据结构,c++,数据结构,算法)