持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情
手把手教学考研大纲范围内树定义,遍历,Huffman,并查集 22考研大纲数据结构要求的是C/C++,笔者以前使用的都是Java,对于C++还很欠缺, 如有什么建议或者不足欢迎大佬评论区或者私信指出 初心是用最简单的语言描述数据结构
Talk is cheap. Show me the code. 理论到处都有,代码加例题自己练习才能真的学会
Huffman的基本结构和操作
先看下Huffman树的结构图:
Huffman树相对于二叉树每个结点多一个`字符`和一个`权重`
权重越大的,越靠近子结点
右面是Huffman树的编码,其实就是结点相对Huffman树根结点的路径(0是选择左结点,1是选择右结点)
Huffman树的特点
没有度为1的结点(每个结点没有子结点,或者有两个子结点,不存在有一个子结点的)
n个叶子结点的哈夫曼树总共有2n-1个结点(Huffman树只有叶子结点才是真的结点,其他结点都是合成结点)
typedef struct TreeNode { //定义Huffman树结构体:字符,权重,huffman编码,左子结点,右子结点
char c;
int weight;
char huffman_code[100];
struct TreeNode *lefttree;
struct TreeNode *righttree;
}TreeNode, *Tree;
struct cmp{ //定义树结点排序规则,按权重从小到大排序
bool operator()(Tree a, Tree b) {
return a->weight > b->weight;
}
};
//huffman树的初始化
void huffmanTreeInit(Tree &tree,unordered_map<char, int> words);
//huffman编码生成
void huffmanCodeInit(Tree &tree);
//打印huffman编码
void huffmanCodePrint(Tree tree);
//得到当前huffman树的深度
int getDepth(Tree tree);
//格式化打印huffman树
void huffmanTreeTypePrint(Tree tree);
Huffman树的初始化
每次都把最小的两个结点合成一个结点,新结点的权值为两个子结点的权值的和,然后把新结点放回原来的地方
我们可以利用层序遍历的方法,把每个结点都放入队列,每次合成取权值最低的两个结点合成,把合成的新结点重新放回队列。
(取最小的两个结点,可以用优先队列,或者每次操作给队列排序,找权值最小的两个结点)
huffman树初始化(代码)
//huffman树的初始化
void huffmanTreeInit(Tree &tree,unordered_map<char, int> words) { //用unordered_map保存每个字符出现的次数
//map和unordered_map差不多的,map是顺序的,unordered_map是按照hash顺序的
//!!!优先队列,符合排序的顺序,但是调试的时候里面的顺序不是排序的,弹出的时候是按照排序后的顺序弹出的
//!!!优先队列,底层为堆排序,是存储找vector或deque里面,所以调试的时候看到的不是排好序的
priority_queue<Tree, vector<Tree>,cmp> nodes;
unordered_map<char, int> :: iterator words_it; //创建一个unordered_map的迭代器
for (words_it = words.begin(); words_it != words.end(); words_it++) { //循环map里面的内容
Tree temp = new TreeNode; //创建树结点,map保存的字符和数量,赋值给树结点
temp->c = words_it->first;
temp->weight = words_it->second;
temp->lefttree = NULL; //左右子结点赋空值
temp->righttree = NULL;
nodes.push(temp); //把创建的树结点保存到优先队列里
}
//优先队列保证队列内得到元素都是按照自定义的排序规则排序的
while (!nodes.empty()) { //只要优先队列不为空,证明还有结点未访问
if (nodes.size() == 1) { //当剩下一个结点的时候,无需在合并,把这个结点给tree,从队列弹出结点
tree = nodes.top();
nodes.pop();
} else {
Tree temp1 = nodes.top(); //取出权重小的两个结点
nodes.pop();
Tree temp2 = nodes.top();
nodes.pop();
Tree newtemp = new TreeNode; //把两个权重小的结点合成一个结点
newtemp->weight = temp1->weight + temp2->weight;
if (temp1->weight < temp2->weight) { //权重小的放到左子结点
newtemp->lefttree = temp1;
newtemp->righttree = temp2;
} else {
newtemp->lefttree = temp2;
newtemp->righttree = temp1;
}
newtemp->c = '0'; //非叶子结点字符赋 0
nodes.push(newtemp); //把新结点入优先队列
}
}
}
Huffman编码初始化
Huffman的编码每个结点是相对于根结点的路径(0代表左结点,1代表右结点)
比如说:A:0110 根结点 ->左结点权重:42 -> 右结点权重:19 -> 右结点权重:8 -> 左结点权重:5,结点A
层序遍历(0代表左结点,1代表右结点)
每层的左子结点为:父结点+0
每层的右子结点为:父结点+1
huffman编码初始化(代码)
//huffman编码生成 0代表左子结点,1代表右子结点
void huffmanCodeInit(Tree &tree) { //用层序遍历,每一层huffman编码多一位
Tree temp = tree;
queue<Tree> nodes;
nodes.push(temp);
while (!nodes.empty()) {
temp = nodes.front(); //访问到当前结点,把当前结点从队列弹出
nodes.pop();
if (temp->lefttree != NULL) { //左子结点不为空,就把左子结点放入优先队列,
nodes.push(temp->lefttree);
strcpy(temp->lefttree->huffman_code, temp->huffman_code); //左结点huffman编码=父结点huffman编码 + 0
char *str = temp->lefttree->huffman_code;
while (*str != '\0') {
*str++;
}
*str = '0';
}
if (temp->righttree != NULL) { //与左子结点同理
nodes.push(temp->righttree);
strcpy(temp->righttree->huffman_code, temp->huffman_code);
char *str = temp->righttree->huffman_code;
while (*str != '\0') {
*str++;
}
*str = '1';
}
}
}