目錄
背景知識:二叉樹的應用、赫夫曼樹。
目的要求:掌握赫夫曼樹和赫夫曼編碼的基本思想和算法的程序實現。
實驗內容:實現文件中數據的加解密與壓縮:將硬盤上的一個文本文件進行加密,比較加密文件和原始文件的大小差別;對加密文件進行解密,比較原始文件和解碼文件的內容是否一致。
實驗說明:
1.輸入和輸出:
(1)輸入:硬盤上給定的原始文件及文件路徑。
(2)輸出:
- 硬盤上的加密文件及文件路徑;
- 硬盤上的解碼文件及文件路徑;
- 原始文件和解碼文件的比對結果。
2.實驗要求:
- 提取原始文件中的數據(包括中文、英文或其他字符),根據數據出現的頻率爲權重,構建Huffman編碼表;
- 根據Huffman編碼表對原始文件進行加密,得到加密文件並保存到硬盤上;
- 將加密文件進行解密,得到解碼文件並保存點硬盤上;
- 比對原始文件和解碼文件的一致性,得出是否一致的結論。
3.參考類型定義 //雙親孩子表示法
typedef struct {
unsigned int weight;
unsigned int parent, lchild, rchild;
} HTNode, *HuffmanTree; //動態分配數組存儲赫夫曼樹
- typedef char * * HuffmanCode; //動態分配數組存儲赫夫曼編碼表
(*≧︶≦))( ̄▽ ̄* )ゞ小嘮叨
這是我們的數據結構實驗二,作爲拖延症患者的我ddl從來都是第一生產力,於是,熬了兩個晚上終於把思路給理出來了。8過代碼還是借鑑啦大佬的博客
(一)第一步就是思路整理
這個實驗難在它對我來說有一些抽象,一開始對文件的壓縮和解壓的概念很模糊,後來找到了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個字節)就可以搞定。而每個字符的編碼可以由哈夫曼編碼得到,每個字符的權值就是它出現的頻率。
實驗中比較重要的兩個步驟就是壓縮和解壓縮:
①壓縮:先打開要壓縮的文件,分析每個字符的總次數,然後將次數傳給哈夫曼樹,構建一顆哈夫曼樹,然後獲取到每個字符的哈夫曼編碼,將編碼寫入文件
②解壓縮:打開要解壓縮的文件,讀出儲存的哈夫曼字典(結構體),然後根據字典逐個的翻譯編碼得到字符,寫入文件。
哈夫曼編碼的思路(來自上述鏈接中大佬的博客): |
||||
|
(二)函數模塊
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;
}