#include <iostream>
#include <cstdlib>
#include <cstring>
#pragma warning(disable:4996) //忽略strcpy的错误
using namespace std;
typedef struct HTNode {
char c; //存储的字符
int weight; //权值
int parent, lchild, rchild; //双亲序号 左孩子和右孩子序号
}HTNode, * HuffmanTree;
typedef char** HuffmanCode; //把HuffmanCode定义为指向char的指针的指针 相当于一个二维数组 列存放每个节点 行存放每个结点的字符
int min(HuffmanTree HT, int k) { //求出权值最小的一个结点
int i = 0;
int minNum, minNum_weight; //分别存放权值最小的结点序号和对应的权值
while (HT[i].parent != 0) i++; //找到第一个权值为0的结点
minNum = i; minNum_weight = HT[i].weight;
for (; i <= k; i++) {
if (HT[i].parent == 0 && HT[i].weight < minNum_weight) {
minNum = i;
minNum_weight = HT[i].weight;
}
} //for
HT[minNum].parent = 1; //找出最小权值的结点之后 将其parent域置为1 不需要知道确切的parent值 做个标记以便区分即可
return minNum;
}
void Select(HuffmanTree HT, int k, int& s1, int& s2) { //求出权值最小的两个结点 调用min()函数两次
s1 = min(HT, k);
s2 = min(HT, k);
}
HuffmanTree CreatHuffmanTree(int n) { //创建HuffmanTree
int s1, s2;
int m = 2 * n - 1; //n代表有多少个根节点要合并 m代表合并完总结点的个数
if (n <= 1) return 0;
HuffmanTree HT = new HTNode[m + 1]; //0号不用 多分配一个存储单元
for (int i = 1; i <= m; i++) { //初始化操作
HT[i].lchild = 0;
HT[i].rchild = 0;
HT[i].parent = 0;
}
for (int i = 1; i <= n; i++) { //赋值操作,输入每个结点的字符以及权值
cin >> HT[i].c >> HT[i].weight;
}
//以上为初始化操作 接下来生成HuffmanTree
for (int i = n + 1; i <= m; i++) {
Select(HT, i - 1, s1, s2); //在HT[k](1<=k<=i-1)中找到两个权值最小的且parent值为0的结点,将这两个结点在数组中的序号值用s1 s2返回
HT[s1].parent = i; HT[s2].parent = i; //从数组中删除s1 s2,利用对其parent赋值的方法
HT[i].lchild = s1; HT[i].rchild = s2; //对新节点的左右孩子赋值
HT[i].weight = HT[s1].weight + HT[s2].weight; //新结点的权值等于左右孩子之和
}
return HT;
}
void HTPrint(HuffmanTree HT, int n) { //打印HuffmanTree
cout << "weight" << "\t\t" << "parent" << "\t\t" << "lchild" << "\t\t" << "rchild" << endl;
for (int i = 1; i <= 2 * n - 1; i++)
cout << HT[i].weight << "\t\t" << HT[i].parent << "\t\t" << HT[i].lchild << "\t\t" << HT[i].rchild << endl;
}
void CreatHuffCode(HuffmanTree HT, HuffmanCode& HC, int n) { //从叶子逆方向回溯到根节点求出原始结点的编码
int location; //code字符数组的下标 每次循环从最后一位开始
int f, c;
char* code; //临时存放编码的字符数组
HC = new char* [n + 1]; //n个结点分配n+1个头指针
code = new char[n];
code[n - 1] = '\0'; //字符数组编码结束符
for (int i = 1; i <= n; i++) { //从1->n 逐个字符求编码
location = n - 1; c = i; f = HT[i].parent;
while (f != 0) {
--location;
if (HT[f].lchild == c) code[location] = '0';
else code[location] = '1';
c = f; f = HT[f].parent; //继续执行 类似于链表的-》next
} //while
HC[i] = new char[n - location]; //为二维数组的行分配空间
strcpy(HC[i], &code[location]); //注意strcpy的用法 里面是地址 HC[i]本身就是地址 对于code[]需要取地址&, 而且code[location]可以从有编码的位置开始赋值 前面的空都不会复制过去
} //for
}
void HCPrint(HuffmanTree HT, HuffmanCode HC, int n) { //打印HuffmanTree中每个原始结点的编码
cout << "字符对应的编码为:" << endl;
for (int i = 1; i <= n; i++) {
cout << "字符" << HT[i].c << "的编码为:" << HC[i] << endl;
}
}
void Encode(HuffmanTree HT, HuffmanCode HC, char str[], int n, int cnt) { //对输入的字符进行编码操作 注意:输入的字符仅限于上述HuffmanTree中的原始字符
for (int i = 0; i < cnt; i++) {
for (int j = 1; j <= n; j++) { //每个都比较一遍
if (str[i] == HT[j].c) {
cout << HC[j];
break;
}
}
}
}
void DeCode(HuffmanTree HT, char charCode[], int n) { //解码操作 利用对编码后的字符从头到尾 在对应的HuffmanTree中深度搜索的方式得到解码字符
int intCode[50];
for (int i = 0; i < strlen(charCode); i++) {
if (charCode[i] == '0') intCode[i] = 0;
else intCode[i] = 1; //对整型数组进行赋值初始化
}
int cnt = 0; //计数已经解码过的个数
while (cnt < strlen(charCode)) {
int j = 2 * n - 1;
while (HT[j].lchild != 0 && HT[j].rchild != 0) {
if (intCode[cnt] == 0) j = HT[j].lchild;
if (intCode[cnt] == 1) j = HT[j].rchild;
cnt++; //一次循环结束 计数加一
}
cout << HT[j].c;
}
}
int main() {
int n; char Receivechar[20];
char charCode[50]; //接受要解码的01字符串
HuffmanCode HC;
cout << "请输入原根结点数n:";
cin >> n;
HuffmanTree HT = CreatHuffmanTree(n); //创建HuffmanTree
CreatHuffCode(HT, HC, n); //生成每个原始结点的HuffmanCode
HTPrint(HT, n); //打印HuffmanTree
HCPrint(HT, HC, n); //打印每个原始结点的HuffmanCode
cout << "请输入要编码的字符:" << endl;
int i = 0;
while ((Receivechar[i] = getchar()) != '#') i++; //i表示多少个字符
cout << "编码为:";
Encode(HT, HC, Receivechar, n, i); //输出编码
cout << endl;
getchar(); //用于接收上一步输入的回车符 防止下面的gets_s接收到这个回车符从而被跳过!!!
cout << "请输入要解码的01串:";
gets_s(charCode); //获取字符串
cout << "解码为:";
DeCode(HT, charCode, n);
}
2021-11-17补充:
以上没有文件操作,如果想把字符的编码和解码内容保存到文件中,可以利用几句文件的操作。
例如:
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ofstream fout;
fout.open("C:/Users/xxx/Desktop/xxx.txt",ios::out);
int n1 = 100;
char name[] = "xxx";
fout<<n1<<endl;
fout<<name<<endl;
fout.close();
}
文件还是要好好学,数据多的时候还能靠控制台输入吗。。。