1.赫夫曼树的基本操作
- 赫夫曼树采用线性结构(即数组)存储最为简单,相关的基本操作有:树的构建、叶子结点的编码、01字符串的译码和字母字符串的编码。
- 每个结点应该包含的信息有:字符内容、父结点和左右孩子结点的下标、权重以及该字符的编码。
struct HFNode{
char ch;
int weight;
int parent, lChild, rChild;
string code;
HFNode(){
parent = 0;
lChild = 0;
rChild = 0;
code = "";
}
};
class HuffmanTree{
HFNode* HT;//根结点
int leafNum;//叶子结点数目
public:
HuffmanTree(){
cin >> leafNum;
HT = new HFNode[2 * leafNum];//0号位置不用
createHFTree();//树的构造
}
void selectMin(int len, int &s1, int &s2){//找最小的两个权值
int min1=9999, min2 = 9999;
for(int i = 1; i < len; ++i){
if(HT[i].parent == 0){
if(HT[i].weight < min1){
min2 = min1;
s2 = s1;
min1 = HT[i].weight;
s1 = i;
}else if(HT[i].weight < min2){
min2 = HT[i].weight;
s2 = i;
}
}
}
}
void createHFTree(){
int i, s1, s2;
for(i = 1; i <= leafNum; ++i){
cin >> HT[i].ch;
}
for(i = 1; i <= leafNum; ++i){
cin >> HT[i].weight;
}
for(i = leafNum + 1; i < 2 * leafNum; ++i){
selectMin(i, s1, s2);
HT[s1].parent = i;
HT[s2].parent = i;
HT[i].lChild = s1;
HT[i].rChild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
}
void HuffmanCoding(){//编码,从叶子到根
int i, j, c, f;
for(i = 1; i <= leafNum; ++i){
string code = "";
for(c = i, f = HT[i].parent; f != 0; c = f, f = HT[f].parent){
if(HT[f].lChild == c){
code += '0';
}else{
code += '1';
}
}
for(j = code.length() - 1; j >= 0; --j){
HT[i].code += code[j];
}
}
}
void HuffmanDecoding(string str){//译码
int i, c, root;
char ch;
string result = "";
root = 2 * leafNum - 1;
c = root;
for(i = 0; i < str.length(); ++i){
ch = str[i];
if(ch == '0'){
c = HT[c].lChild;
}else if(ch == '1'){
c = HT[c].rChild;
}else{//非法字符处理
cout << "error!" << endl;
break;
}
if(HT[c].lChild == 0 && HT[c].rChild == 0){//到达叶子结点,回溯到根结点
result += HT[c].ch;
c = root;
}else{//标志字符串不完整的情况
ch = '\0';
}
}
if(ch == '\0'){
cout << "error!" << endl;
}else{
cout << result << endl;
}
}
void transferToCode(string str){//提供一串文字,获取对应的赫夫曼编码
int i, j;
for(i = 0; i < str.length(); ++i){
for(j = 1; j <= leafNum; ++j){
if(str[i] == HT[j].ch){
cout << HT[j].code;
break;
}
}
}
};
2.赫夫曼树与二叉树的应用
(1) 计算带权路径和(APL)
- 已知一棵二叉树的叶子权值,该二叉树的带权案路径和APL等于叶子权值乘于根节点到叶子的分支数,然后求总和。
- 注意:这里的赫夫曼树采用前面二叉树的方法构建,因此是链表结构。
如下图中,叶子都用大写字母表示,权值对应为:A-7,B-6,C-2,D-3
树的带权路径和 = 7*1 + 6*2 + 2*3 + 3*3 = 34
int result = 0;//全局变量
//这里的count是路径数,初始传入0
void findAPL(BiNode* T, int count){
if(T){
if(T->lChild == 0 && T->rChild == 0){
result += T->weight * count;
}
findAPL(T->lChild, count + 1);
findAPL(T->rChild, count + 1);
}
}
(2) 求二叉树最大路径
- 二叉树的每个结点都有一个权值,从根结点到每个叶子结点将形成一条路径,每条路径的权值等于路径上所有结点的权值和。编程求出二叉树的最大路径权值。
如下图所示,共有4个叶子即有4条路径,
路径1权值=5 + 4 + 11 + 7 = 27
路径2权值=5 + 4 + 11 + 2 = 22
路径3权值=5 + 8 + 13 = 26
路径4权值=5 + 8 + 4 + 1 = 18
可计算出最大路径权值是27。
该树输入的先序遍历结果为ABCD00E000FG00H0I00,各结点权值为:
A-5,B-4,C-11,D-7,E-2,F-8,G-13,H-4,I-1
- 这里二叉树的结构要添加权重,设权重值在输入先序遍历字符串后输入。
int max = 0;//全局变量
struct BiNode{
char data;
int weight;
BiNode* lChild;
BiNode* rChild;
BiNode(){
weight = 0;
lChild = NULL:
rChild = NULL;
}
};
void createBiTree(BiNode* T){
char ch;
cin >> ch;
if(ch == '0'){
T = NULL;
}else{
T->data = ch;
createBiTree(T->lChild);
createBiTree(T->rChild);
}
}
void inputWeight(BiNode* T){//先序遍历输入权重
if(T){
cin >> T->weight;
inputWeight(T->lChild);
inputWeight(T->rChild);
}
}
void getMax(BiNode* T){
if(T){
if(!T->lChild){
T->lChild->weight += T->weight;
}
if(!T->rChild){
T->rChild->weight += T->weight;
}
max = max > T->weight ? max : T->weight;//取最大路径
getMax(T->lChild);
getMax(T->rChild);
}
}
算法思路:将一条路径中所有父结点的权重加到叶子结点上,就是这条路径的路径权值。用一个全局变量max去比较每次遍历到的结点的权重,取最大值,最终能找到最大路径权值。