二叉树与赫夫曼图片压缩实践--数据结构--C语言、c++语言版--可运行

实验目标
1 、掌握树的存储结构。
2 、掌握二叉树的三种遍历方法。
3 、掌握 Huffman 树、 Huffman 编码等知识和应用。
4 、使用 C++ 、文件操作和 Huffman 算法实现“图片压缩程序”专题编程。
实验任务
压缩软件是利用特定算法来压缩数据的工具,压缩后生成的文件称为压缩包 (archive)
如果想使用其中的数据,就得用压缩软件对数据进行解压。利用压缩软件对文件中重复的数
据进行压缩,可以减小文件中的字节总数,使文件能够通过互联网连接实现更快传输,此外
还可以减少文件的磁盘占用空间。常用的压缩软件有 rar zip 等。
压缩可以分为无损压缩与有损压缩两种。无损压缩后的文件,经过解压能够完全恢复原
始数据;有损压缩的文件则无法完全恢复。 rar zip 等格式都是无损压缩格式。音乐文件格
mp3 、图片文件格式 jpg 都是有损压缩格式。
计算机文件是由一个个字节组成的, 1 个字节有 0~255 256 种可能的取值,每个字节
的编码长度都是 8 位。由于文件中的字节总是会重复出现,可以对不同的字节设计长度不等
的编码,让出现次数较多的字节,采用尽可能短的编码,那么文件编码的总长便可减少。
统计文件中 256 种不同的字节重复的次数,以每种字节重复的次数作为权值 (weight)
构造一棵有 256 个叶子节点的二叉树。若带权路径长度达到最小,称这样的二叉树为最优二
叉树,即 Huffman (Huffman tree)
Huffman 树从根到每个叶子都有一条路径。对路径上的各分支,约定指向左子树根的分
支编码为 “0” ,指向右子树根的分支编码为 “1” 。从根到每个叶子相应路径上的 “0” “1” 组成
的序列,就是这个叶子节点的编码,称为 Huffman 编码。

废话少说,直接上代码

数据流头文件:BitIO.h

#pragma once
#pragma warning(disable:4996)
#ifndef HUFFMANCOMPRESSCPRO1_BITIO_H
#define HUFFMANCOMPRESSCPRO1_BITIO_H

#include <iostream>
#define BITBUFFSIZE 1024
#define SHIFT 3
struct BIT {
    char b[BITBUFFSIZE];//位数组 bit数组
    int p; //指示数组填到哪一位的下一位
};

//向位数组栈顶推入一位
bool pushBit(BIT* buffer, const bool istrue);

//从文件加载多位
int fPushBit(BIT* buffer, FILE* fp);

//修改position位置的一位
int changeBit(BIT* buffer, const int istrue, const int position);

//读取一位
int readBit(BIT* buffer, const int position);

//栈顶弹出一位
int popBit(BIT* buffer);

#endif

压缩程序头文件:Compress.h

#pragma once
#pragma warning(disable:4996)
#ifndef HUFFMANCOMPRESSCPRO1_COMPRESS_H
#define HUFFMANCOMPRESSCPRO1_COMPRESS_H

void Compress(char* Filename);

#endif

哈夫曼树头文件:Huffman.h

#pragma once
#pragma warning(disable:4996)
#ifndef HUFFMANCOMPRESSCPRO1_HUFFMANTREE_H
#define HUFFMANCOMPRESSCPRO1_HUFFMANTREE_H
#include <string>
#include <iostream>
#include "BitIO.h"
#define SIZE 256
#define NODES 2*SIZE - 1

//取出index位,若取出的index位为0,则GET_BYTE值为假,否则为真
#define GET_BYTE(vByte, index) (((vByte) & (1 << ((index) ^ 7))) != 0)
//把index位设置为‘1’
#define SET_BYTE(vbyte, index) ((vbyte) |= (1 << ((index) ^ 7)))
//把index位设置为‘0’
#define CLR_BYTE(vbyte, index) ((vbyte) &= (~(1 << ((index) ^ 7))))

//n个叶子结点的哈夫曼树共有2n-1个结点
//一个字节8位 读取文件的char可以从0-255 用weight[]数组记录每个char出现的频率
//HuffmanTree 保存在HTNode[2n-1]的数组中
struct Node {
    bool isLeaf();
    int ch = 0;
    int weight = 0;
    int lChild = 0, rChild = 0, parent = 0;
};
typedef Node HTNode, * HuffTree;

struct HEAD {
    char type[4];
    int length;
    int weight[256];
};

void expand();

void select(HuffTree HT, int end, int* s1, int* s2);

void buildCode(std::string st[], Node x, std::string s, HuffTree HT);

std::string* buildCode(Node root, HuffTree HT);

void testBuildCode(std::string* st);

void InitTrie(int* w, HuffTree& HT);

void buildTrie(int* w, HuffTree& HT);

void testBuildTrie(int* w, HuffTree HT);

int InitHead(const char* Filename, HEAD& sHead);

unsigned char Str2Byte(char* BinStr);

void writeTrie(Node x, FILE* fpo, HuffTree& HT);

void encode(FILE* fpi, FILE* fpo, std::string* st);

void writeHead(FILE* fpo, HEAD& head);

int Extract();

#endif

三个头文件结束

头文件解释:

#pragma once

防止出现重复头文件

#pragma warning(disable:4996)

解决可能会出现的4996问题

错误 C4996 'scanf': This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.

 

数据流源文件:BitIO.cpp

#include "BitIO.h"
bool pushBit(BIT* buffer, const bool istrue) {
    if (buffer->p >= BITBUFFSIZE * 8)
        return EOF;
    else if (istrue)
        buffer->b[buffer->p >> SHIFT] |= 128u >> buffer->p % 8; //p所指位置填1
    else
        buffer->b[buffer->p >> SHIFT] &= ~(128u >> buffer->p % 8); //p所指位置填0
    buffer->p++;
    return istrue;
}
int fPushBit(BIT* buffer, FILE* fp) {
    memset(buffer, 0, sizeof(BIT));
    if (buffer->p = fread(buffer->b, sizeof(char), BITBUFFSIZE, fp) && feof(fp)) {
        buffer->p = (buffer->p - 2) * 8 + buffer->b[buffer->p - 1] + 1;
    }
    else
        buffer->p *= 8;
    return buffer->p;
}
int changeBit(BIT* buffer, const int istrue, const int position) {
    if (position >= buffer->p)
        return EOF;
    else if (istrue)
        buffer->b[position >> SHIFT] |= 128u >> position % 8;
    else
        buffer->b[position >> SHIFT] &= ~(128u >> position % 8);
    return istrue;
}
int readBit(BIT* buffer, const int position) {
    if (position >= buffer->p)
        return EOF;
    else
        return buffer->b[position >> SHIFT] & (128u >> position % 8);
}
int popBit(BIT* buffer) {
    if (buffer->p >= BITBUFFSIZE || buffer->p < 0)
        return EOF;
    buffer->p--;
    return buffer->b[(buffer->p + 1) >> SHIFT] & (128u >> (buffer->p + 1) % 8);
}

压缩程序源文件:Compress.cpp

#include "Compress.h"
#include "Huffman.h"

void Compress(char* Filename) {
    int weight[SIZE] = { 0 };
    FILE* fin = fopen(Filename, "rb");
    if (!fin) {
        std::cerr << "File opening failed" << std::endl;
        exit(EXIT_FAILURE);
    }
    int ch;
    while ((ch = fgetc(fin)) != EOF) {
        weight[ch]++;
    }
    fclose(fin);

    HuffTree HT;
    InitTrie(weight, HT);
    buildTrie(weight, HT);
    testBuildTrie(weight, HT);

    std::string st[SIZE];
    buildCode(st, HT[NODES - 1], "", HT);
    std::cout << "HuffmanCode: " << std::endl;
    std::cout << "Bytes \t Codes" << std::endl;
    for (int i = 0; i < SIZE; i++) {
        printf("0x%02X :", i);
        printf("%s\n", st[i].c_str());
    }
    //testBuildCode(str);
//    std::cout<<"HuffmanCode: "<<std::endl;
//    std::cout<<"Bytes \t Codes"<<std::endl;
//    for(int i= 0; i < SIZE; i++){
//        printf("0x%02X :", i);
//        printf("%s\n",str[i].c_str());
//    }

    HEAD head;
    InitHead(Filename, head);

    char filename[256] = { '\0' };
    strcpy(filename, Filename);
    strcat(filename, ".huf");

    FILE* out = fopen(filename, "wb");
    writeHead(out, head);

    fin = fopen(Filename, "rb");
    encode(fin, out, st);

    fclose(fin);
    fclose(out);

    Extract();
}

哈夫曼树源文件:Huffman.cpp

#include "Huffman.h"
//HT指向HuffmanTree end为保存树数组所需搜寻的末尾,s1,s2分别指向最小和次小结点在树数组中下标
void select(HuffTree HT, int end, int* s1, int* s2) {
    int min1, min2;
    int i = 0;

    while (HT[i].parent != 0 && i <= end) {
        //找到第一个无双亲结点
        i++;
    }

    min1 = HT[i].weight;
    *s1 = i;

    i++;
    while (HT[i].parent != 0 && i <= end) {
        //第二个无双亲结点
        i++;
    }

    if (HT[i].weight < min1) {
        min2 = min1;
        min1 = HT[i].weight;
        *s2 = *s1;
        *s1 = i;
    }
    else {
        min2 = HT[i].weight;
        *s2 = i;
    }

    for (int j = i + 1; j <= end; j++) {
        //两结点与后续无双亲结点比较
        if (HT[j].parent != 0) {
            continue;
        }
        if (HT[j].weight < min1) {
            //新结点小于min1
            min2 = min1;
            *s2 = *s1;
            min1 = HT[j].weight;
            *s1 = j;
        }
        else if (HT[j].weight >= min1 && HT[j].weight < min2) {
            //新结点介于min1,min2 之间
            min2 = HT[j].weight;
            *s2 = j;
        }
    }
}

void InitTrie(int* w, HuffTree& HT) {
    HT = (HuffTree)malloc(sizeof(HTNode) * NODES);
    for (int i = 0; i < SIZE; i++) {
        //0-255 叶子结点
        HT[i].parent = 0;
        HT[i].rChild = 0;
        HT[i].lChild = 0;
        HT[i].weight = w[i];
        HT[i].ch = i;
    }
    for (int i = SIZE; i < NODES; i++) {
        //256-510 内部结点
        HT[i].parent = 0;
        HT[i].lChild = 0;
        HT[i].rChild = 0;
        HT[i].weight = 0;
        HT[i].ch = 0;
    }
}

void buildTrie(int* w, HuffTree& HT) {
    //每次搜寻两棵频率最小的树合并
    int s1, s2;
    for (int i = SIZE; i < NODES; i++) {
        //从256开始填树结点,一直到把根结点510填满 NODES = 511
        select(HT, i - 1, &s1, &s2);
        HT[s1].parent = HT[s2].parent = i;
        HT[i].lChild = s1;
        HT[i].rChild = s2;
        HT[i].weight = HT[s1].weight + HT[s2].weight;
    }
}

void testBuildTrie(int* w, HuffTree HT) {
    std::cout << "字节ASCII " << "频率" << std::endl;
    //文件中出现的字节对应的ASCII(十六进制)和 出现的频率
    for (int i = 0; i < SIZE; i++) {
        printf("0x%02X %d\n", i, w[i]);
    }
    //哈夫曼树中所有结点
    printf("结点下标\t频率\t双亲下标\t左孩子下标\t右孩子下标\n");
    for (int i = 0; i < NODES; i++) {
        printf("HT[%d]\t%d\t%d  \t%d  \t\t%d\t%d\n", i, HT[i].weight, HT[i].parent, HT[i].lChild, HT[i].rChild, HT[i].ch);
    }
}

bool Node::isLeaf() { return lChild == 0 && rChild == 0; }

void buildCode(std::string st[], Node x, std::string s, HuffTree HT) {
    if (x.isLeaf()) {
        st[x.ch] = s;
        return;
    }
    buildCode(st, HT[x.lChild], s + '0', HT);
    buildCode(st, HT[x.rChild], s + '1', HT);
}

std::string* buildCode(Node root, HuffTree HT) {
    std::string st[SIZE];
    buildCode(st, root, "", HT);
    std::cout << "HuffmanCode: " << std::endl;
    std::cout << "Bytes \t Codes" << std::endl;
    for (int i = 0; i < SIZE; i++) {
        printf("0x%02X :", i);
        printf("%s\n", st[i].c_str());
    }
    return st;
}

void testBuildCode(std::string* st) {
    std::cout << "HuffmanCode: " << std::endl;
    std::cout << "Bytes \t Codes" << std::endl;
    for (int i = 0; i < SIZE; i++) {
        printf("0x%02X :", i);
        printf("%s\n", (st + i)->c_str());
    }
}

int InitHead(const char* Filename, HEAD& sHead) {
    //初始化文件头
    char* t = (char*)malloc(sizeof(char) * strlen(Filename));
    strcpy(t, const_cast<char*>(Filename));
    char* token = std::strtok(t, ".");
    std::string str[4];
    int it = 0;
    while (token != nullptr) {
        str[it++] = token;
        token = strtok(NULL, ".");
    }
    char t2[8] = { '\0' };
    for (int k = 0; k < str[it - 1].size(); k++) {
        t2[k] = str[it - 1].at(k);
    }
    strcpy(sHead.type, t2);
    sHead.length = 0;
    for (int i = 0; i < 256; i++) {
        sHead.weight[i] = 0;
    }

    //以二进制流打开文件
    FILE* in = fopen(Filename, "rb");
    int ch;
    while ((ch = fgetc(in)) != EOF) {
        sHead.weight[ch]++;
        sHead.length++;//原文件长度多少字节
    }
    fclose(in);
    in = nullptr;
    return 1;
}

//将8位char变成一个char 即把 char数组 中的 char01 变成 一个char 中的 位01
unsigned char Str2Byte(char* BinStr) {
    unsigned char b = 0x00;
    for (int i = 0; i < 8; i++)
    {
        b = b << 1;
        if (BinStr[i] == '1')
        {
            b = b | 0x01;
        }
    }
    return b;
}

//可以将编码好的单词查找树写在文件开头,代价是文件变大, 也可以利用文件头(上述HEAD)中的weight数组在解压时重新构建树
//按位写入查找树
void writeTrie(Node x, FILE* fpo, HuffTree& HT) {
    char ch;
    if (x.isLeaf()) {
        bool t = true;
        fwrite(&t, sizeof(bool), 1, fpo);
        ch = (char)x.ch;
        fwrite(&ch, sizeof(char), 1, fpo);
        return;
    }
    bool t = false;
    fwrite(&t, sizeof(bool), 1, fpo);
    writeTrie(HT[x.lChild], fpo, HT);
    writeTrie(HT[x.rChild], fpo, HT);
}

//原文件编码后写入压缩文件
void encode(FILE* fpi, FILE* fpo, std::string* st) {
    int ch;
    char cd[SIZE] = { 0 };
    int pos = 0;
    //unsigned char pBuffer[1000] = {0}; //debug
    if (!fpo) {
        std::cerr << "outFile opening failed" << std::endl;
        exit(EXIT_FAILURE);
    }
    while ((ch = fgetc(fpi)) != EOF) {
        std::string code = st[ch];
        strcat(cd, code.c_str());

        while (strlen(cd) >= 8) {
            unsigned char w = Str2Byte(cd);
            //pBuffer[pos++] = w;
            fwrite(&w, sizeof(char), 1, fpo);//写入文件
            for (int i = 0; i < SIZE - 8; i++) {
                //cd整体向左偏移8位
                cd[i] = cd[i + 8];
            }
        }
    }
    //处理可能剩余不足8位
    if (strlen(cd) > 0) {
        unsigned char w = Str2Byte(cd);
        fwrite(&w, sizeof(char), 1, fpo); //最后不足8位的位都为0
    }
}

void writeHead(FILE* fpo, HEAD& head) {
    fwrite(&head, sizeof(head), 1, fpo);
}

int Extract() {
    std::cout << "File to extract: ";
    char efile[256];
    std::cin >> efile;
    FILE* in = fopen(efile, "rb");
    if (!in) {
        std::cerr << "File opening failed" << std::endl;
        return EXIT_FAILURE;
    }
    HEAD head;
    fread(&head, sizeof(HEAD), 1, in);
    char file[SIZE] = { '\0' };
    char* t = (char*)malloc(sizeof(char) * strlen(efile));
    strcpy(t, const_cast<char*>(efile));
    char* token = strtok(t, ".");
    strcpy(file, token);
    strcat(file, "_extracted.");
    strcat(file, head.type);
    FILE* out = fopen(file, "wb");

    fseek(in, 0L, SEEK_END);
    long filesize = ftell(in);

    HuffTree HT;
    InitTrie(head.weight, HT);
    buildTrie(head.weight, HT);
    std::string st[SIZE];
    buildCode(st, HT[NODES - 1], "", HT);

    fseek(in, 1032, SEEK_SET);
    long curLoc = ftell(in);

    unsigned char outValue;
    int index = 0;
    int root = NODES - 1;
    unsigned char ch = fgetc(in);
    fseek(out, 0, SEEK_SET);
    while (true) {
        if (HT[root].lChild == 0 && HT[root].rChild == 0) {
            outValue = (char)HT[root].ch;
            fwrite(&outValue, sizeof(char), 1, out);
            if (curLoc >= filesize) {
                break;
            }
            root = NODES - 1;
        }
        if (!(ch & (1 << (index ^ 7)))) {
            root = HT[root].lChild;
        }
        else {
            root = HT[root].rChild;
        }

        if (++index >= 8) {
            index = 0;
            ch = fgetc(in);
            curLoc = ftell(in);
        }
    }

    fclose(in);
    fclose(out);
    return 1;
}

main程序源文件:main.cpp

#include <iostream>
#include "Huffman.h"
#include "Compress.h"
using namespace std;
int main() {
    cout << "==== 哈夫曼压缩 ====" << endl;
    cout << "请输入你的文件名: ";
    char filename[SIZE];
    cin >> filename;
    Compress(filename);//解压函数也在这里运行了
    return 0;
}

程序到此结束

下面是使用以及效果:

示例图片

首先将图片保存到文件目录下,保存名字1.jpg

运行界面

 

输入文件名: 1.jpg

输出(很长,截取一段)

输出文件在当前目录下:

打开文件查看:

为了验证解压缩的文件是否正确,还需要将文件的大小保存到文件中去,同时也可以将

文件的类型存入文件中,用于判断是否是需要解压的文件,这里默认的是 HUF 类型。

结束

 

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.实验目的 通过“图片压缩编码”的编程实践,学习树、遍历二叉树哈夫曼树、哈夫曼编码和他们的编程应用。 (1)掌握树的存储结构 (2)掌握二叉树的三种遍历方法 (3)掌握并理解Huffman树、Huffman编码等知识和应用 (4)掌握文件的操作 (5)使用Huffman算法实现图像压缩程序 2.实验内容 使用Huffman压缩算法,对一幅BMP格式的图片文件进行压缩。图片文件名为“Pic.bmp”,压缩后保存为“Pic.bmp.huf”文件。使用VS2010作为开发工具,开发一个控制台程序,使用Huffman压缩算法对图片文件“Pic.bmp”进行压缩。具体要求如下: (1)读取原文件,统计权值: 运行程序,输入文件名。 以“Pic.bmp”文件为例。若文件存放在F盘根目录下,输入文件完整路径“F:\Pic.bmp”。按回车结束。以字节流的方式,只读打“Pic.bmp”文件。 逐字节读取文件,统计文件中256种字节重复的次数,保存到一个数组中int weight[256]中。 (2)生成Huffman树 根据(1)中统计的结果,构建Huffman树。定义一个结构体来记录每个节点的权值、父节点、左孩子和右孩子。使用结构体数组来存储这个Huffman树。 (3)生成Huffman编码 遍历(2)中生成的Huffman树,记录256个叶子节点的Huffman编码,保存在字符串数组中。 (4)压缩原文件 使用Huffman编码对原文件中的字节重新编码,获得压缩后的文件数据。 (5)保存压缩文件 将编码过的数据,保存到文件“Pic.bmp.huf”中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值