之前提到的都是线性数据结构,树是非线性数据结构,可以很好描述层序关系
二叉树
二叉树是最最常用的树形数据结构
二叉树每个节点最多有两个子节点,称为左孩子和右孩子,他们的子树分别称为左子树和右子树。
二叉树第i层最多🈶️2^(i-1)个节点。如果树的所有节点都是满的那么为满二叉树,如果只有最后一层有节点缺失则为完全二叉树。
二叉树从根到某个节点的路径长度为该节点的深度,某个节点到它叶子节点的最大路径为该节点的高度,根节点高度最大
二叉树的访问效率极高
二叉树的存储
动态二叉树:
struct node{
int data;
node *l,*r;
};
每次使用申请节点,用后删除
静态数组存储(常用)
struct node{
char value;
int l,r
}tree[N];
二叉树与树
树转二叉树
1 先把来自同一个父节点的子节点从左到右连上
2 所有节点的子节点只连左边第一个节点 别的删掉
3 然后整理

二叉树转树

1 若某结点的左孩子结点存在,将左孩子结点的右孩子结点、右孩子结点的右孩子结点......都作为该结点的孩子结点,将该结点与这些右孩子结点用线连接起来;
2 删除原二叉树中所有结点与其右孩子结点的连线;
3 整理
二叉树的遍历
宽度优先遍历(BFS)
从第一层到最后一层从上到下从左到右逐个遍历
深度优先遍历(DFS)
包括前序遍历,中序遍历,后序遍历
前序遍历
根➡️左子树➡️右子树

void pre(node *root){
cout<<root->data;
pre(root->l);递归左子树
pre(root->r);递归右子树
}
中序遍历

void in(node *root){
in(root->l);递归左子树
cout<<root->data;
in(root->r);递归右子树
}
后序遍历
从左到右从下到上先子节点再父节点,最后根节点
void in(node *root){
in(root->l);递归左子树
in(root->r);递归右子树
cout<<root->data;
}
已知中序遍历+前序遍历或者后序遍历 都可以构造出一个树
只有前序遍历和后序遍历无法构造出一棵树
哈夫曼树与哈夫曼编码
哈夫曼树是带权路径最短的树,是贪心思想与二叉树的结合,
哈夫曼编码则是哈夫曼树的经典应用,可以理解为对于比较频繁出现的信息,用尽量少的位数去表示;不常出现的信息,用大的空间来表示
比如我们现在想存储以下这串数据
AAABBBBBBBBBCCCCCCDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEEEE
整理后我们得到这串数据有3个A 9个B 6个C 15个D 19个E,共6个字母,我们想用二进制编码表示这些数,我们马上可以想到用相同长度的二进制01来表示
即000为A 001为B 010为C 011为D 100为E
那么这种情况下假设每个二进制数占用一个存储单位,那么现在我们总占用内存为3*(3+9+6+5+19)=156
这种编码虽然很好想也很简单但实际上可能会占用大量空间,聪明的你肯定想到,让个数多的字母用少的空间,让个数少的字母用多的空间,比如E用0表示,D用1表示,A用100来表示
但这样会出现另一个新的问题,如果给你一个二进制数100,这个100表示的是什么?是A?还是DEE。因此我们能发现编码不能随便编,不然会出现混淆。那我们应该怎么解决
我们用二叉树来构造编码:即用哈夫曼树来构建哈夫曼编码

我们让出现频次高的字符靠近根节点,出现频次少的远离根节点,用0表示左节点,1表示右节点
我们得到了A(1100),B(111),C(1101),D(10),E(0)
接下来计算一下他们的空间大小:3*4+3*9+4*6+15*2+19*1=112 此时答案为最优
题目练习
洛谷P1087(橙题)

首先我们仔细读题,根据题目中给出的递归构造方法与给出的输入输出样例,我们自己进行模拟一下(画图)

进行后序遍历后发现我们得到的输出与题目所给一致,那么我们只需用二叉树模拟题目过程即可,
#include<bits/stdc++.h>
using namespace std;
char ver(string s) {
bool F=0,B=0,I=0;
for (int i=0;i<s.length();i++) {
if (s[i]=='0')B=1;//判断B
if (s[i]=='1')I=1;//判断I
if (B&&I)F=1;//判断F
}
if (F==1)return 'F';//return F的判断放在第一个!
if (B==1)return 'B';
if (I==1)return 'I';
}
void tree(string s) {//递归树解决
if (s.size()==1) {//递归停止条件为字符串大小为1
cout<<ver(s);
return;
}
int l=s.size()/2;//每次字符串大小减半
tree(s.substr(0,l));//截取前半字符串
tree(s.substr(l));//拿剩下的字符串 即截取后半字符串
cout<<ver(s);//后序遍历所以cout放在最后
}
int main() {
int n;
string s;
cin>>n>>s;
tree(s);
return 0;
}
洛谷P1030(橙题)
需要注意到
后序遍历的最后一个元素是树的根
中序遍历中左右子树被根给分隔开
左 / 右子树的中序序列长度 = 左 / 右子树的后序序列长度
知道这三条性质后我们再去画树就有思路了
我们先用后序序列找到该子树的根,并寻找它在中序序列中的位置。
接下来我们分别截取左右子树的中序序列和后序序列,分别对它们进行建树操作。
直到建树操作完成后,才停止递归
#include<bits/stdc++.h>
using namespace std;
struct node {
char data;
int l,r;
}tree[1001]; // 足够存储常规二叉树节点
int pos = 0; // 显式初始化,更规范
// 递归构建二叉树:中序in + 后序post → 返回根节点编号
int planttree(string in,string post) {
int len = in.length();
if(len == 0) return 0; // 空树返回0
char c = post[len-1]; // 后序最后一位是当前根
int root = ++pos; // 分配当前根的唯一编号
tree[root].data = c; // 存储根节点值
int k = in.find(c); // 找到根在中序中的位置
// 递归构建左子树:中序[0,k) + 后序[0,k)
tree[root].l = planttree(in.substr(0,k), post.substr(0,k));
// 递归构建右子树:中序[k+1,end) + 后序[k, len-k-1)
tree[root].r = planttree(in.substr(k+1), post.substr(k, len-k-1));
return root; // 返回当前根节点编号
}
// 递归输出前序遍历结果(根→左→右)
void out(int root) {
if (root == 0) return; // 空节点直接返回,避免越界
cout << tree[root].data; // 输出根
out(tree[root].l); // 递归输出左子树
out(tree[root].r); // 递归输出右子树
}
int main() {
string in, post;
cin >> in >> post;
// 1. 构建树,接收根节点编号
int root = planttree(in, post);
// 2. 调用输出函数,输出前序遍历结果(核心修复点)
out(root);
return 0;
}
洛谷P2168(蓝题)哈夫曼树

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
struct Node {
ll weight;
ll height;
bool operator<(const Node& other) const {
if (weight != other.weight) {
return weight > other.weight;
}
return height > other.height;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
ll n, k;
cin >> n >> k;
priority_queue<Node> pq;
for (ll i = 0; i < n; ++i) {
ll w;
cin >> w;
pq.push({w, 1});
}
if ((n - 1) % (k - 1) != 0) {
ll add_count = k - 1 - (n - 1) % (k - 1);
for (ll i = 0; i < add_count; ++i) {
pq.push({0, 1});
}
}
ll total_weight = 0;
ll max_height = 0;
while (pq.size() > 1) {
ll sum_w = 0;
ll current_max_h = LLONG_MIN;
for (ll i = 0; i < k; ++i) {
Node top = pq.top();
pq.pop();
sum_w += top.weight;
if (top.height > current_max_h) {
current_max_h = top.height;
}
}
pq.push({sum_w, current_max_h + 1});
total_weight += sum_w;
if (current_max_h > max_height) {
max_height = current_max_h;
}
}
cout << total_weight << '\n' << max_height << endl;
return 0;
}