一. 问题描述
在一个加密应用中,要处理的信息来自下面的字符集,各个字符的相关使用频度如下:
字符空格A B C D E F G H I J K L M
频度 180 64 13 23 32103 22 15 47 57 1 5 31 20
字符 N O P Q R S T U V W X Y Z
频度 55 63 15 1 48 56 80 25 7 18 2 16 1
现请编写程序你实现如下功能:
(1)运行时,由用户输入来初始化字符集大小和相应用字符。
(2)输入一个要加密的字符串,将其加密。
(3)输出解密字符串。
二. 问题分析
字符加密,利用哈弗曼编码对其进行加密。确定字符和权值,选择最小两个根子树,筛选致最后一棵树。将其左子树赋值为0,右子树赋值为1,再对哈夫曼树进行遍历,方可得到加密后的字符集。哈夫曼算法Huffman Tree
void HuffmanTree(element huffTree[],int w[],int n)
{
for(i=0;i<2*n-1;i++)
{
huffTree[i].parent=-1;
huffTree[i].lchild=-1;
huffTree[i].rchild=-1;
}
for(i=0;i<n;i++)
huffTree[i].weight=w[i];
for(k=n;k<2*n-1;k++)
{
Select(huffTree,i1,i2);
huffTree[i1].parent=k;
huffTree[i2].parent=k;
huffTree[k].weight=huffTree[i1].weight+huffTree[i2].weight;
huffTree[k].lchild=i1;
huffTree[k].rchild=i2;
}
}
三.算法设计
给定字符集的哈夫曼树生成后,依次以叶子T[i](0≤i≤n-1)为出发点,向上回溯至根为止。上溯时走左分支则生成代码0,走右分支则生成代码1。每次最小权值的前面两个链表结点中的树结点组成一个子树,子树有合权值,子数的根按权值排序进链表
树的带权路径长度记为WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln),N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。
四. 时间复杂度和空间复杂度分析
(1)时间复杂度:由于采取的是哈弗曼编码,构造哈弗曼树后,得到哈弗曼编码需要遍历每一个结点,在程序设计中共有26个结点,从根节点开始进行前序遍历,因此所花费的时间复杂度为O(n)。
(2)空间复杂度:空间复杂度是指程序运行所需要的内存空间,我们需要存放的有结点数据及关系,所以所需要内存空间为一个常数,所以空间复杂度为 O(1)。
五.源代码
#ifndef INFO_TREE_H
#define INFO_TREE_H
#include <fstream>
#include <vector>
#include <string>
#include <map>
using namespace std;
struct TNode
{
string Alph;
unsigned Rate;
TNode *Left;
TNode *Right;
string Code;
bool Flag;
TNode(string, unsigned);
TNode(unsigned, TNode*, TNode*);
};
class Tree
{
friend class Info;
public:
Tree(ifstream &file);
string Encrypt(string);
string Decrypt(string);
private:
vector<TNode*>::iterator Min();
TNode* Huffman();
void Fill_Table();
vector<TNode*> Node;
TNode *Root;
map<string, string> Alpha_Table;
};
#endif
#include "Info_Tree.h"
#include "Stack.h"
#include <iostream>
TNode::TNode(string alph, unsigned rate) :
Alph(alph), Rate(rate), Left(nullptr), Right(nullptr), Flag(true){}
TNode::TNode(unsigned rate, TNode *left, TNode *right) :
Alph("#"), Rate(rate), Left(left), Right(right), Flag(true){}
vector<TNode*>::iterator Tree::Min()
{
vector<TNode*>::iterator min, iter;
min = Node.begin();
for (iter = Node.begin(); iter != Node.end(); ++iter)
{
if ((*iter)->Rate < (*min)->Rate)
{
min = iter;
}
}
return min;
}
TNode* Tree::Huffman()
{
vector<TNode*>::iterator iter;
TNode *left = nullptr, *right = nullptr;
TNode *r = nullptr;
while (Node.size() > 1)
{
iter = Min();
right = *iter;
Node.erase(iter);
iter = Min();
left = *iter;
Node.erase(iter);
r = new TNode(left->Rate + right->Rate, left, right);
Node.push_back(r);
}
return r;
}
void Tree::Fill_Table()
{
TNode *t = Root;
Root->Flag = false;
Stack<TNode*> stack;
stack.Push_back(t);
do
{
if (t->Left != nullptr)
{
stack.Push_back(t);
if (t->Left->Flag)
{
t->Left->Code += t->Code + "0";
t->Left->Flag = false;
}
t = t->Left;
}
else if (t->Right != nullptr)
{
if (t->Right->Flag)
{
t->Right->Code += t->Code + "1";
t->Right->Flag = false;
}
t = t->Right;
}
else
{
Alpha_Table[t->Alph] = t->Code;
t = stack.Pop();
if (t->Right->Flag)
{
t->Right->Code += t->Code + "1";
t->Right->Flag = false;
}
t = t->Right;
}
} while (!stack.Empty());
}
Tree::Tree(ifstream &file) :
Root(nullptr)
{
if (!file)
{
cout << "无法打开字符配置文件" << endl;
system("pause");
exit(-1);
}
TNode *t = nullptr;
string alph;
unsigned rate;
while (!file.eof())
{
file >> alph >> rate;
t = new TNode(alph, rate);
Node.push_back(t);
}
Root = Huffman();
Fill_Table();
}
string Tree::Encrypt(string key)
{
unsigned iter = 0;
string s;
while (iter != key.size())
{
char k = key[iter++];
string tmp;
if (k > 'a'&&k < 'z')
k -= 32;
tmp += k;
s += Alpha_Table[tmp];
}
return s;
}
string Tree::Decrypt(string key)
{
string::iterator iter;
string tmp;
TNode *t = Root;
for (iter = key.begin(); iter != key.end() && t != nullptr;)
{
if (*iter == '0'&&t->Left != nullptr)
{
t = t->Left;
++iter;
}
else if (*iter == '1'&&t->Right != nullptr)
{
t = t->Right;
++iter;
}
if (t->Left == nullptr&&t->Right == nullptr)
{
tmp.append(t->Alph);
t = Root;
}
}
return tmp;
}
#include "Info_Tree.h"
#include <iostream>
using namespace std;
int main(void)
{
int select = 1; // 用户功能选择号
char source[256], destination[256]; // 源串与目标串
Encrypt objEncrypt; // 加密类对象
while (select != 3)
{
// 选择菜单
cout << "请选择" << endl;
cout << "1. 加密--将输入的文本串进行加密后输出" << endl;
cout << "2. 解密--将输入的已加密的文本进行解密后输出" << endl;
cout << "3. 退出--退出运行" << endl;
cin >> select;
switch (select)
{
case 1: // 加密
cout << "请输入文本串:";
cin >> source; // 输入文本串
strcpy(destination, objEncrypt.Encode(source).CStr()); // 加密
cout << "加密串:" << destination << endl << endl;// 输出加密串
break;
case 2: // 解密
cout << "请输入加密串:";
cin >> source; // 输入文本串
strcpy(destination, objEncrypt.Decode(source).CStr()); // 解密
cout << "解密串:" << destination << endl << endl;// 输出解密串
break;
}
}
system("PAUSE"); // 调用库函数system()
return 0; // 返回值0, 返回操作系统
}
#ifndef STACK_H
#define STACK_H
template<typename T>
class Stack
{
public:
struct SNode
{
T data;
SNode *next;
SNode() :next(nullptr){}
};
Stack();
~Stack();
bool Empty();
void Push_back(T);
T Pop();
private:
SNode *First;
SNode *Rear;
};
template<typename T>
Stack<T>::Stack() :
First(nullptr), Rear(nullptr)
{
First = new SNode;
Rear = new SNode;
First->next = Rear;
Rear->next = nullptr;
}
template<typename T>
Stack<T>::~Stack()
{
while (!Empty())
{
SNode *d = First;
First = First->next;
delete d;
}
delete First;
delete Rear;
}
template<typename T>
bool Stack<T>::Empty()
{
if (First->next == Rear)
return true;
else return false;
}
template<typename T>
void Stack<T>::Push_back(T t)
{
SNode *s = new SNode;
s->data = t;
s->next = First->next;
First->next = s;
}
template<typename T>
T Stack<T>::Pop()
{
if (!Empty())
{
SNode *s = First->next;
T tmp = s->data;
First->next = s->next;
delete s;
return tmp;
}
else
{
cout << "栈为空,无法弹出" << endl;
system("pause");
exit(-1);
}
}
#endif
六.心得
该程序在调试之初有许多错误,但在努力之下和舍友帮助之下都被一一告破,现在操作该程序时已经能根据相关人性化提示进行操作,并得出正确结果。克服了不能调试,不能获得正确结果,不能正确加密等问题,但经过努力已得完善。在该次课程设计中,了解到了许多自身在c++以及数据结构概念理解上的不足,以及自身在程序设计上的不细心;今后还需改进。预祝老师春节快乐。