哈夫曼算法证明
哈夫曼算法是一种贪心算法,我们考虑证明其最优子结构和贪心选择性质:
-
最优子结构:假设一个树是哈夫曼树,则以其任意节点为根节点的最大子树也是哈夫曼树。
证明:子树的根节点的值是其所有叶子节点出现权值之和,因此无论子树是什么形式,对子树上方的节点计算WPL2都没有影响。
根据哈夫曼树的定义:WPL最小的二叉树。如果子树不是哈夫曼树,其WPL1就不会是最小,那么整个树的WPL=WPL1+WPL2就不会是最小,这与哈夫曼树的定义相悖,因此子树是哈夫曼树。
-
贪心选择性质(哈夫曼算法):每次去掉权值最低的两个节点作为叶子节点形成一颗二叉树,并将其父亲节点放入待选节点中。
证明:对于哈夫曼树我们总可以通过取出两个节点作为叶子节点并将其父亲节点重新作为叶子节点的方式构造,需要证明的是是否每次选择权值最低的节点可以构造成功。
当含有2个及以下的叶子节点的时候,显然正确。
假设当含有小于k个叶子节点的时候哈夫曼算法可以形成哈夫曼树
对于含有k个叶子节点形成的树,设其中权重最小的叶子节点为a,其中深度最深的叶子节点为b,h表示节点的深度,w表示节点的权值。则:
W P L 1 = ∑ + w a ∗ h a + w b ∗ h b WPL_1=\sum+w_a*h_a+w_b*h_b WPL1=∑+wa∗ha+wb∗hb
交换这两个节点:
W P L 2 = ∑ + w b ∗ h a + w a ∗ h b WPL_2=\sum+w_b*h_a+w_a*h_b WPL2=∑+wb∗ha+wa∗hb
W P L 1 − W P L 2 = ( w a − w b ) ∗ h a + ( w b − w a ) ∗ h b = ( h b − h a ) ∗ ( w b − w a ) WPL_1-WPL_2=(w_a-w_b)*h_a+(w_b-w_a)*h_b=(h_b-h_a)*(w_b-w_a) WPL1−WPL2=(wa−wb)∗ha+(wb−wa)∗hb=(hb−ha)∗(wb−wa)
而 h b − h a h_b-h_a hb−ha和 w b − w a w_b-w_a wb−wa都为正(由a,b节点的性质),所以我们得到结论:对于任何一棵树,将权值小的节点尽可能移动到较深层的节点会使整个树的WPL比较小。
对于k个节点,我们首先取出两个节点,将其合并成一个节点以后我们有k-1个节点,可以使用哈夫曼算法构造。
如果这两个节点不是所有节点中权值最小的两个,则我们总可以通过交换使得构造的树的WPL减小,因此不是哈夫曼树。只有两个节点是所有节点中权值最小的两个时我们无法再降低树的WPL。因为我们总可以构造成功,所以选择权值最小的节点构造的树就是哈夫曼树。证毕。
哈夫曼编码译码程序
#pragma once
#include<iostream>
#include<fstream>
#include<vector>
#include<queue>
#include<cstdio>
#include<string>
using namespace std;
static const int MAXN = 1005;
struct times
{
double weight;//字符的权值
int num;//字符的序号,同时也是他的ASCALL值
times(int _num=0,double _weight=0) :num(_num),weight(_weight){}
friend bool operator < (const times & a,const times & b)
{
return a.weight > b.weight;
}
}p,q;
struct Chara:times
{
int father;//父亲的序号
int lson, rson;//左右儿子的序号
string code;
Chara(int _num = 0, double _weight = 0, int _lson = 0, int _rson = 0, int _father = 0) :times(_num,_weight), lson(_lson), rson(_rson), father(_father)
{
code = "";//没有编码
}
void operator = (const Chara& x)
{
weight = x.weight; num = x.num; father = x.father; lson = x.lson; rson = x.rson;
}
};
struct txt
{
string t;
double weight;
txt(string _t,double _weight):t(_t),weight(_weight){}
};
class HuffmanCode
{
public:
int n=0;//字符的个数
int cur;//当前所在位置
int root;//哈夫曼树根节点
Chara A[MAXN];//顺序表保存哈夫曼树
priority_queue<times> T;//用来构建哈夫曼树
void CreatHuffmanCode(int x, string now)
{
if (A[x].lson != 0)
{
CreatHuffmanCode(A[x].lson, now + "0");
}
if (A[x].rson != 0)
{
CreatHuffmanCode(A[x].rson, now + "1");
}
if (A[x].lson == 0 && A[x].rson == 0)//说明是字符
{
A[x].code = now;
}
}
void _HuffmanCode(int _n,vector<txt>& input)
{
string tmp;
n = _n;
for (int i = 0; i < n; i++)
{
tmp = input[i].t;
A[tmp[0]].num = tmp[0];
A[tmp[0]].weight = input[i].weight;
T.push(times(tmp[0], A[tmp[0]].weight));
}
cur = 500;
//构建哈夫曼树
while (T.size() > 1)
{
p = T.top(); T.pop(); q = T.top(); T.pop();
A[cur] = Chara(cur, p.weight + q.weight, p.num, q.num, 0);
A[p.num].father = A[q.num].father = cur;
T.push(times(A[cur])); cur++;
}
T.pop(); root = cur-1;
CreatHuffmanCode(root, "");
}
};
class Huffman
{
int n;
vector<txt> input;
HuffmanCode x;
public:
void _Huffman()
{
string t; double weight; n = 0;
FILE* stream;
freopen_s(&stream,"hfmTree.txt","r",stdin);
while (1)
{
cin >> t;
if (t == "Esc") break;
n++;
cin >> weight;
input.push_back(txt(t, weight));
}
freopen_s(&stream, "CON", "r", stdin);
input.push_back(txt(" ", 10000.0));//给空格很大的权值
n++;
x._HuffmanCode(n, input);
}
Huffman()
{
string t; double weight; n = 0;
cout << "请输入字符集 \n[Delete撤销输入,Esc退出输入]\n[直接输入Default按照默认文件组成哈夫曼编码]\n[空格已经编码]"<< endl;
while(1)
{
cout << "请输入字符:";
cin >> t;
if (n == 0 && t == "Default")//按照默认文件构造哈夫曼树
{
_Huffman();
return;
}
else if (t == "Delete")
{
input.erase(input.end()-1);//删除最后一个输入的字符
n--;
continue;
}
else if (t == "Esc")
{
break;
}
n++;
cout << "请输入" << t[0] << "的权重:";
cin >> weight;
input.push_back(txt(t, weight));
}
FILE* stream;
freopen_s(&stream, "hfmTree.txt", "w", stdout);
for (int i = 0; i < n; i++)
{
cout << input[i].t << " " << input[i].weight << endl;
}
cout << "Esc" << endl;
freopen_s(&stream, "CON", "w", stdout);
input.push_back(txt(" ", 10000.0));//给空格很大的权值
n++;
x._HuffmanCode(n, input);
}
void HuffmanDisplay()
{
cout << "哈夫曼编码:" << endl;
for (int i = 0; i < n; i++)
{
cout << input[i].t[0] << ":" << x.A[input[i].t[0]].code << endl;
}
}
void GenerateCode()//压缩文件
{
cout << "请输入需要压缩文件的路径[输入Default将打开默认文件ToBeTran.txt]" << endl;
string in;
cin >> in;
if (in == "Default")
{
in = "ToBeTran.txt";
}
cout << "请输入保存压缩后文件的路径[输入Default将打开默认文件CodeFile.txt]" << endl;
string out;
cin >> out;
if (out == "Default")
{
out = "CodeFile.txt";
}
ifstream infile(in, ios::in);
ofstream outfile(out, ios::out);
char c;
bool flag = true;
while ((c = infile.get()) != EOF)
{
if (x.A[c].code == "")
{
flag = false;
break;
}
outfile << x.A[c].code;
}
infile.close();
outfile.close();
if (!flag)
{
cout << "压缩失败,文件中出现了字符集中未包含的字符" << endl;
return;
}
//展示压缩的结果:
infile.open(out, ios::in);
string tmp;
infile >> tmp;
infile.close();
cout << "编码后的文件为:" << endl;
for (int i = 0; i < tmp.size(); i++)
{
if (i && i % 50 == 0) cout << endl;
cout << tmp[i];
}
cout << endl;
//将结果放入文件中
outfile.open("CodePrint.txt", ios::out);
for (int i = 0; i < tmp.size(); i++)
{
if (i % 50 == 0) outfile << endl;
outfile << tmp[i];
}
outfile.close();
}
void Decode()
{
cout << "请输入需要解码文件的路径[输入Default将打开默认文件CodeFile.txt]" << endl;
string in;
cin >> in;
if (in == "Default")
{
in = "CodeFile.txt";
}
cout << "请输入保存压缩后文件的路径[输入Default将打开默认文件TextFile.txt]" << endl;
string out;
cin >> out;
if (out == "Default")
{
out = "TextFile.txt";
}
ifstream infile(in, ios::in);
string ss;
infile >> ss;
//cout << "test:" << ss << endl;
int i = 0;
string sss;
while (i < ss.size())
{
int t = x.root;
while ((x.A[t].lson != 0 || x.A[t].rson != 0) && i < ss.size())
{
if (ss[i] == '0') t = x.A[t].lson;
else t = x.A[t].rson;
i++;
}
if (x.A[t].lson == 0 || x.A[t].rson == 0)
{
sss = sss + (char)t;
}
}
infile.close();
cout << "解码后为:" << endl;
cout << sss << endl;
ofstream outfile(out,ios::out);
outfile << sss << endl;
outfile.close();
}
};
测试代码
#include"Huffman.h"
#include<iostream>
using namespace std;
int main()
{
Huffman x;
x.HuffmanDisplay();
x.GenerateCode();
x.Decode();
}