贪心算法-哈夫曼编码

时间:2021-09-08 11:09:59

《算法导论》引理16.2:令C为一个字母表,其中每个字符c属于C都有一个频率c.freq。令x和y是C中频率最低的两个字符,那么存在C的一个最优前缀码,x和y的码字长度相同,且只有最后一个二进制位不同。(证明问题具有贪心选择性质

引理16.3 令C为一个给定的字符表,其中每个字符c属于C都定义了一个频率c.freq。令x和y是C中频率最低的两个字符。令C1为C去掉字符x和y,加入一个新字符z后得到的字母表。这里z.freq=x.freq+y.freq。令T1为C1的任意一个最优前缀对应的编码树。于是我们将T1中叶结点z替换为一个以x和y为孩子的内部结点,得到树T,T表示字母表C的一个最优前缀码。(证明了构造最优前缀码的问题具有最优子结构性质

字母表每个元素的结构

struct word {
char c;
int freq;
string b;
string code;
int parents;
int left;
int right;
word() {
c = ' ';
parents = -1;
left = -1;
right = -1;
}
word(char ch, int f) :c(ch), freq(f) {
parents=-1;
left=-1;
right=-1;
}
};
//构造哈夫曼树
word huffman(word *w,int n) {
int s = n;
for (int i = 1; i <= n-1; i++) {
int minL = 0, minR = 0;
for (int j = 1; j <= s; j++) {
//数组中的最小值
if (w[j].freq < w[minL].freq&&w[j].parents == -1)
minL = j;
}
for (int j = 1; j <= s; j++) {
//数组中的次小值(可以与最小值相等)
if (w[j].freq >= w[minL].freq&&w[j].freq<w[minR].freq&&j!=minL&&w[j].parents == -1)
minR = j;
}
s++;
w[s].freq = w[minL].freq + w[minR].freq;
w[s].left = minL;
w[s].right = minR;
w[minL].parents = s;
//左孩子与父结点连接的边对应编码0
w[minL].b = "0";
w[minR].parents = s;
//右孩子与父结点连接的边对应编码1
w[minR].b = "1";
}
//if (s == 2 * n - 1) cout << "-----------right";
cout << w[s].freq;
return w[s - 1];
}

void huffmanCode(word *w,int n) {
for (int i = 1; i <= n; i++) {
int j = i;
while (w[j].parents != -1) {
w[i].code += w[j].b;
j = w[j].parents;
}
//因为上面的循环过程是从叶子结点向根节点遍历,因此最后得到的编码要将顺序调换
reverse(w[i].code.begin(), w[i].code.end());
}
}

int main() {
//这个元素便于后面比较
word w0(' ', 0x7fffffff);
word w1('a', 45);
word w2('b', 13);
word w3('c', 12);
word w4('d', 16);
word w5('e', 9);
word w6('f', 5);
int n = 6;
//哈夫曼树一定有2*n-1个结点,将树的结点都存到数组w中
word w[12] = { w0,w1,w2,w3,w4,w5,w6 };
huffman(w, 6);
huffmanCode(w, 6);
return 0;

}