【赫夫曼編碼】實現文件中數據的加解密與壓縮

目錄

(一)第一步就是思路整理

(二)函數模塊

(三)主要數據類型與變量

1、定義哈夫曼樹的結構

2、動態分配數組存儲赫夫曼編碼表

3、存儲數據掃描統計結果

4、主要變量

(四)代碼測試

1、方案

建三個文件

2、運行結果

壓縮文件

解壓文件

輸出文件

比較文件

(五)源碼部分


背景知識:二叉樹的應用、赫夫曼樹。

目的要求:掌握赫夫曼樹和赫夫曼編碼的基本思想和算法的程序實現。

實驗內容:實現文件中數據的加解密與壓縮:將硬盤上的一個文本文件進行加密,比較加密文件和原始文件的大小差別;對加密文件進行解密,比較原始文件和解碼文件的內容是否一致。

實驗說明:

1.輸入和輸出:

(1)輸入:硬盤上給定的原始文件及文件路徑。

(2)輸出:

  • 硬盤上的加密文件及文件路徑;
  • 硬盤上的解碼文件及文件路徑;
  • 原始文件和解碼文件的比對結果。

2.實驗要求:

  • 提取原始文件中的數據(包括中文、英文或其他字符),根據數據出現的頻率爲權重,構建Huffman編碼表;
  • 根據Huffman編碼表對原始文件進行加密,得到加密文件並保存到硬盤上;
  • 將加密文件進行解密,得到解碼文件並保存點硬盤上;
  • 比對原始文件和解碼文件的一致性,得出是否一致的結論。

3.參考類型定義   //雙親孩子表示法      

        typedef struct {

            unsigned int  weight;

            unsigned int  parent, lchild, rchild;

        } HTNode, *HuffmanTree;      //動態分配數組存儲赫夫曼樹

  • typedef char * * HuffmanCode;  //動態分配數組存儲赫夫曼編碼表

    (*≧︶≦))( ̄▽ ̄* )ゞ小嘮叨

 

這是我們的數據結構實驗二,作爲拖延症患者的我ddl從來都是第一生產力,於是,熬了兩個晚上終於把思路給理出來了。8過代碼還是借鑑啦大佬的博客

https://blog.csdn.net/qq_43492703/article/details/99769317?utm_medium=distribute.pc_relevant.none-task-blog-OPENSEARCH-4&depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-4


 

(一)第一步就是思路整理

這個實驗難在它對我來說有一些抽象,一開始對文件的壓縮和解壓的概念很模糊,後來找到了B站上的一個視頻(https://www.bilibili.com/video/BV1Yt411Y76w?from=search&seid=11711631498115172127),把這個本來對我來說比較抽象的概念具體化了:假設文件中的內容是“abcdabccccca”12個字符,一個ascii碼對應一個字節,那麼就是12個字節。但是如果用少於1個字節的幾個位來表示各個字符,列如a:10,c:11,b:01,c:00,那麼算下來只需要不到12個字節(5-6個字節)就可以搞定。而每個字符的編碼可以由哈夫曼編碼得到,每個字符的權值就是它出現的頻率。

實驗中比較重要的兩個步驟就是壓縮和解壓縮:

①壓縮:先打開要壓縮的文件,分析每個字符的總次數,然後將次數傳給哈夫曼樹,構建一顆哈夫曼樹,然後獲取到每個字符的哈夫曼編碼,將編碼寫入文件

②解壓縮:打開要解壓縮的文件,讀出儲存的哈夫曼字典(結構體),然後根據字典逐個的翻譯編碼得到字符,寫入文件。

哈夫曼編碼的思路(來自上述鏈接中大佬的博客):

1、讀取文件並存儲到vector容器中

2、將vector容器中的數據按照以下TNode結構存儲

typedef struct {/*存儲數據掃描統計結果*/

    char* data;/*表示文件中的字符*/

    int* cou;/*同一個字符在文件中出現的次數*/

    int length;/*文件的字符總長度*/

}TNode;

3、使用TNode將哈夫曼樹的葉節點初始化

4、構造哈夫曼樹 得到哈夫曼編碼

(二)函數模塊

void table();/*歡迎界面*/

void ReadFile(vector<char>& f);/*讀取文件到vector容器中*/

void InitList(TNode& N);/*初始化TNode結構*/

int Find(TNode N, char ch);/*查看TNode中是否有ch;有:返回true;無:返回false*/

void WriteTNode(vector<char> v, TNode& N);/*核心:將vector中的數據存入TNode結構體中*/

void Select(HuffmanTree& HT, int n, int& min1, int& min2);/*查找HT中未被使用的權值最小的兩個點的下標*/

void HuffmanCoding(HuffmanTree& HT, HuffmanCode& HC, TNode N);/*建樹函數,附帶完成每一個字符對應的編碼*/

void ZipFile(HuffmanCode HC, vector<char> v, TNode N);/*壓縮文件*/

void RZipFile(HuffmanCode HC, TNode N);/*解壓文件*/

void OutPutFile(vector<char>& f);/*輸出文件到屏幕*/

void CompareFile(vector<char>& f, vector<char>& v);/*對比兩個文件*/

(三)主要數據類型與變量

1、定義哈夫曼樹的結構

typedef struct

{

    unsigned int weight;/*權值*/

    unsigned int parent, lchild, rchild;/*父節點 左節點 右節點*/

}HTNode, * HuffmanTree;/*動態分配數組存儲赫夫曼樹*/

2、動態分配數組存儲赫夫曼編碼表

typedef char** HuffmanCode;

3、存儲數據掃描統計結果

typedef struct {

    char* data;/*表示文件中的字符(相當於哈夫曼樹的結點)*/

    int* cou;/*同一個字符在文件中出現的次數*/

    int length;/*文件的字符總長度*/

}TNode;

4、主要變量

//HT:哈夫曼樹;

//HC:哈夫曼編碼

vector<char> V;/*vector聲明*/

(四)代碼測試

1、方案

  • 建三個文件

路徑分別是:

  • E:\\Amusement\\Test.txt 原始文件 填寫好內容
  • E:\\Amusement\\ZipFile.txt 壓縮後的文件 初始爲空
  • E:\\Amusement\\RZipFile.txt 解壓後的文件 初始爲空

  • 測試文件Test 包含文字和字母以及符號

2、運行結果

  • 壓縮文件

  • ZipFile中儲存壓縮後的內容

  • 解壓文件

  • 輸出文件

  • 比較文件

(五)源碼部分

好啦廢話不多說啦直接上源碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <fstream>
#include <Windows.h>

using namespace std;

/*-----主要數據類型與變量------*/
typedef struct/*定義哈夫曼樹的結構*/
{
    unsigned int weight;/*權值*/
    unsigned int parent, lchild, rchild;/*父節點 左節點 右節點*/
}HTNode, * HuffmanTree;/*動態分配數組存儲赫夫曼樹*/

typedef char** HuffmanCode;/*動態分配數組存儲赫夫曼編碼表*/

typedef struct {/*存儲數據掃描統計結果*/
    char* data;/*表示文件中的字符(相當於哈夫曼樹的結點)*/
    int* cou;/*同一個字符在文件中出現的次數*/
    int length;/*文件的字符總長度*/
}TNode;

void table() {
    cout << "1、壓縮文件" << endl;
    cout << "2、解壓文件" << endl;
    cout << "3、輸出文件" << endl;
    cout << "4、比較文件" << endl;
    cout << "5、  退出  " << endl;
}

void ReadFile(vector<char>& f) {/*讀取文件*/
    char path[500];/*文件路徑*/
    char ch;
    cout << "請輸入您文件的路徑:" << endl;
    cin >> path;/*從鍵盤輸入路徑*/

    /*定義一個來自於文件path的輸入copy流,
    用ios::in方式 打開,in方式表示要讀取文件,
    文件不存在的話,不建立文件,而是得到一個空的ifstream對像*/
    ifstream infile(path, ios::in);
    if (!infile) {/*文件不存在*/
        cout << "wrong open!!" << endl;
        exit(1);/*退出當前運行的程序,並將參數1返回給主調進程*/
    }
    while (infile.peek() != EOF) {/*讀操作,返回下一個字符但是不讀取它*/
        infile.get(ch);/*讀取字符賦值給ch*/
        f.push_back(ch);/*把字符ch推入vector*/
    }
    infile.close();/*關閉文件*/
    cout << "完成read" << endl;
    system("pause");
}

void InitList(TNode& N) {/*初始化TNode定義的結點*/
    N.data = new char[256];
    N.cou = new int[256];
    if (!N.data || !N.cou) exit(1);
    N.length = 0;
}

int Find(TNode N, char ch) {/*查看TNode中是否有ch;有:返回true;無:返回false*/
    for (int i = 0; i < N.length; i++)
        if (ch == N.data[i]) return true;/*ch在TNode存在*/
    return false;/*ch在TNode中不存在*/
}

/*核心:將vector中的數據存入TNode結構體中*/
void WriteTNode(vector<char> v, TNode& N) {
    char ch;/*存儲臨時字符*/
    int len = v.size(), j = 0;
    for (int i = 0; i < len; i++) {/*遍歷vector容器 尋找相同的字符*/
        ch = v[i];/*相同的字符暫時存入v這個容器中*/
        if (!Find(N, ch)) {
            /*如果大vector中不再有沒有讀入v的相同的字符
            那麼就將v中當前字符ch的數據存入TNode N中*/
            N.data[j] = ch;/*當前字符*/
            N.cou[j] = count(v.begin(), v.end(), ch);/*此字符出現了多少次*/
            j++;
            N.length++;
        }
    }
    cout << "完成write" << endl;
    system("pause");
}

/*查找HT中未被使用的權值最小的兩個點的下標*/
void Select(HuffmanTree& HT, int n, int& min1, int& min2) {
    /*min1, min2最小值和次小值對應的下標*/
    min1 = min2 = 0;/*初始化*/
    for (int i = 1; i < n; i++) {
        if (HT[i].parent != 0) continue;/*略過已經加入的結點*/
        if (min1 == 0) min1 = min2 = i;/*賦初值*/
        else if (HT[i].weight <= HT[min1].weight) {/*min1是最小值*/
            min2 = min1; min1 = i;
        }
        else if (HT[i].weight < HT[min2].weight) {/*min2是次小值*/
            min2 = i;
        }
        else if (HT[i].weight > HT[min2].weight) {/*防止兩個值相等*/
            if (min1 == min2) min2 = i;
        }
    }
}

/*建樹函數,附帶完成每一個字符對應的編碼*/
void HuffmanCoding(HuffmanTree& HT, HuffmanCode& HC, TNode N) {
    int m, start, n = N.length;
    char* cd;
    unsigned int c, f;
    if (n <= 1) return;
    m = 2 * n - 1;/*實際需要的存儲空間爲m*/
    HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));/*0號單元未用 從1開始*/
    for (int i = 1; i <= n; i++) {/*利用TNode將葉節點初始化*/
        HT[i].parent = 0;
        HT[i].lchild = HT[i].rchild = 0;
        HT[i].weight = N.cou[i - 1];/*TNode從0開始存儲 HT從1開始存儲*/
    }
    for (int i = n + 1; i <= m; i++) {/*初始化非葉節點*/
        HT[i].parent = HT[i].weight = 0;
        HT[i].lchild = HT[i].rchild = 0;
    }
    for (int i = n + 1; i <= m; i++) {/*構建赫夫曼樹*/
        int min1, min2;/*選出最小的兩個結點合併*/
        Select(HT, i, min1, min2);
        HT[i].weight = HT[min1].weight + HT[min2].weight;
        HT[i].lchild = min1;
        HT[i].rchild = min2;
        HT[min1].parent = HT[min2].parent = i;
    }
    /*從葉子到根逆向求每個字符的赫夫曼編碼*/
    HC = (HuffmanCode)malloc((n + 1) * sizeof(char*));/*分配n個字符編碼的頭指針向量*/
    cd = (char*)malloc(n * sizeof(char));/*分配求編碼的工作空間*/
    cd[n - 1] = '\0';/*編碼結束符*/
    for (int i = 1; i <= n; i++) {/*逐個字符求赫夫曼編碼*/
        start = n - 1;/*編碼結束符位置*/
        for (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));/*爲第i個字符編碼分配空間*/
        strcpy(HC[i], &cd[start]);/*從cd複製編碼(串)到HC*/
    }
    free(cd);/*釋放工作空間*/
    cout << "完成Huffman" << endl;
    system("pause");
}

void ZipFile(HuffmanCode HC, vector<char> v, TNode N) {/*壓縮文件*/
    int i = 0, j = 0, k = 0;
    ofstream outfile("E:\\Amusement\\ZipFile.txt", ios::out);/*文件以輸出方百式打開*/
    if (!outfile) {/*文件爲空*/
        cerr << "wrong open!!" << endl;
        exit(1);/*出現錯誤,返回值1*/
    }
    for (i = 0; i < v.size(); i++) {/*遍歷vector容器*/
        for (j = 0; j < N.length; j++)
            if (N.data[j] == v[i]) break;
        for (k = 0; HC[j + 1][k] != '\0'; k++)/*藉助哈夫曼編碼將數據壓縮*/
            outfile << HC[j + 1][k];
    }
    outfile.close();/*關閉文件*/
    cout << "Zipping..."; Sleep(200); cout << "..."; Sleep(200); cout << "...";/*讓用戶等得一些時間*/
    cout << "Finished! You can find your new file at E:\\Amusement\\ZipFile.txt" << endl;/*提示完成壓縮*/
    system("pause");
}

void RZipFile(HuffmanCode HC, TNode N) {/*解壓文件*/
    int flag, flag2 = 0, m = 0, i, j;
    char ch;
    char ch2[55];
    ofstream outfile("E:\\Amusement\\RZipFile.txt", ios::out);/*文件以輸百出方式打開;如果沒有文件,那麼生成空文件*/
    ifstream infile("E:\\Amusement\\ZipFile.txt", ios::in);/*如果沒有文件,打開失敗;不能寫文件*/
    if (!outfile) {/*文件打開失敗*/
        cerr << "wrong open!!" << endl;
        exit(1);/*運行錯誤,返回值1*/
    }
    if (!infile) {
        cerr << "wrong open!!" << endl;
        exit(1);/*運行錯誤,返回值1*/
    }
    while (infile.peek() != EOF) {
        flag = 0;
        char* cd = new char[N.length];
        for (i = 0;; i++) {
            infile >> ch;
            cd[i] = ch;
            cd[i + 1] = '\0';
            for (int j = 1; j <= N.length; j++) {
                if (strcmp(HC[j], cd) == 0) {
                    if (flag2 == 1) {
                        ch2[m] = N.data[j - 1];
                        flag = 1;
                        m++;
                        delete cd;
                        break;
                    }
                    if (flag2 == 0) {
                        outfile << N.data[j - 1];
                        flag = 1;
                        delete cd;
                        break;
                    }
                }
            }
            if (flag == 1)
                break;
        }
    }
    cout << "RZipping..."; Sleep(200); cout << "..."; Sleep(200); cout << "...";/*讓用戶等得一些時間*/
    cout << "Finished! You can find your new file at E:\\Amusement\\RZipFile.txt" << endl;/*提示完成解壓縮*/
}

void OutPutFile(vector<char>& f) {
    char path[200];/*文件路徑*/
    char ch;
    cout << "請輸入您所要查詢的文件路徑" << endl;
    cin >> path;/*從鍵盤輸入路徑*/

    ifstream infile(path, ios::in);
    if (!infile) {/*文件不存在*/
        cout << "wrong open!!" << endl;
        exit(1);/*退出當前運行的程序,並將參數1返回給主調進程*/
    }
    while (infile.peek() != EOF) {/*讀操作,返回下一個字符但是不讀取它*/
        infile.get(ch);/*讀取字符賦值給ch*/
        f.push_back(ch);/*把字符ch推入vector*/
    }
    infile.close();/*關閉文件*/
    cout << "路徑:" << path << "的文件如下" << endl;
    for (int i = 0; i < f.size(); i++)
        cout << f.at(i);
    cout << endl;
    f.clear();
    system("pause");
}

void CompareFile(vector<char>& f, vector<char>& v) {
    cout << ">>請依次輸入您的兩個待比較文件" << endl;
    ReadFile(f);
    ReadFile(v);
    //if (f.size() != v.size()) {
    //    cout << "您所比較的兩個文件不相同" << endl;
    //    return;
    //}
    /*一直在想如何逐一比較vector裏面的數據,後來查了博客才知道可以直接比較*/
    if (f == v)
        cout << "您所比較的兩個文件相等" << endl;
    else
        cout << "您所比較的兩個文件不相等" << endl;
    return;
}

int main() {
    int choose;
    vector<char> V, Vf, Vs, Vr;
    HuffmanTree HT;
    HuffmanCode HC;
    TNode N;
    InitList(N);
    while (1) {
        system("cls");
        table();
        cout << "請輸入您的操作:" << endl;
        cin >> choose;
        switch (choose) {
        case 1:
            cout << "-----將爲您進行壓縮文件操作-----" << endl;
            ReadFile(V);/*打開指定文件 讀取數據 存入vector容器*/
            WriteTNode(V, N);/*將vector中的數據存儲到NTode結構中*/
            HuffmanCoding(HT, HC, N);/*將TNode中的數據存到哈夫曼樹中並生成哈夫曼編碼*/
            ZipFile(HC, V, N);/*壓縮文件*/

            break;
        case 2:
            cout << "-----將爲您進行解壓縮文件操作-----" << endl;
            RZipFile(HC, N);/*對文件內容進行解碼*/
            system("pause");
            break;
        case 3:
            cout << "-----將爲您進行輸出文件操作-----" << endl;
            OutPutFile(Vr);/*將文件內容藉助vector輸出到終端*/
            break;
        case 4:
            cout << "-----將爲您進行解比較文件操作-----" << endl;
            CompareFile(Vf, Vs);
            system("pause");
            //cout << ">>請依次輸入您的兩個待比較文件" << endl;
            //OutPutFile(Vf);
            //OutPutFile(Vs);
            /*直接將兩個待比較的文件輸出 肉眼進行比較*/
            break;
        case 5:
            return 0;
        default:
            cout << "請輸入規範的序號";
            system("pause");
            break;
        }
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章