本篇 核心知识点 :树基础通用概念、二叉树全套知识(定义 / 名词 / 数学公式 / 存储方式 / 堆)、二叉树四种遍历、二叉树递归方法(高度 / 节点数 / 插入删除)、层级遍历
一、树通用基础概念(所有树共用)
1. 概念
树属于非线性分层结构,节点一对多,任意两个节点有且仅有一条唯一通路;多条通路则为图结构。
2. 核心基础特性
-
一棵树有且仅有 1 个根节点,存储时仅需保存根指针;
-
根无父节点,其余节点有唯一直接父节点;
-
一个父节点可分出多个子树分支;
-
子树之间互相独立。
3. 专业名词(必背)
-
父 / 双亲节点:直接包含当前子节点的上层节点;
-
子节点:父节点分出的下层节点;
-
兄弟节点:同一个父节点的所有子节点;
-
节点的度:该节点拥有子节点的数量;
-
叶子节点(叶节点):度 = 0,无任何子节点;
-
树的高度 / 深度:从根到最远叶子节点的最大层数(默认层数从 1 开始计算)。
4. 拓展:树的工业用途
树结构核心优势是查找效率远高于链表(对数级复杂度),游戏、引擎大量使用:
四叉树:2D 地图、地形遮挡渲染优化;
八叉树:3D 空间场景裁剪、AI 寻路分区;
二叉树:数据检索、排序堆。
二、二叉树专项(重点)
2.1 二叉树定义
概念
所有节点的度 ≤ 2,每个节点最多分出左、右两个子树,左右子树严格区分,不可互换。
数学公式
-
二叉树第 k 层,最多节点数:(2^{k-1})
-
k 层二叉树,整棵树最大总节点:(2^k -1)
-
通用恒等式:
叶子节点个数 = 度为 2 节点个数 + 1
已知总节点数 M、度 1 节点 N,可反推叶子、二度节点数量。
2.2 满二叉树 & 完全二叉树
1)满二叉树
概念
每一层都达到最大节点容量,无空缺,不存在只有单侧子树的节点。
特性:总节点一定满足 (2^k-1)。
2)完全二叉树
概念
将满二叉树从最下层最右侧依次删除节点得到的树;空缺仅允许出现在最后一层右侧。
硬性规则
若节点无左子树,则一定无右子树;不可能出现有右无左的节点。
核心价值
支持数组顺序存储(普通二叉树不适合顺序存储)
2.3 二叉树两种存储方式
方式 1:链式存储(手写二叉树默认方案)
概念
节点包含数据、左孩子指针、右孩子、可选父指针;树类仅保存根节点。
特性
任意形态二叉树均可存储,无空间浪费,增删灵活。
代码示例(带父指针节点模板)
template<typename T>
struct BinNode
{
T data;
BinNode<T>* left; // 左子节点
BinNode<T>* right; // BinNode<T>* parent; // 父节点指针
BinNode(const T& val) : data(val), left(nullptr), right(nullptr), parent(nullptr) {}
};
template<typename T>
class BinTree
{
private:
BinNode<T>* root; // 根节点
public:
BinTree() : root(nullptr) {}
~BinTree() { /* 递归释放所有节点 */ }
void preOrder(BinNode<T>* p); // 前序遍历
int getHeight(BinNode<T>* p); // 求树高
void insert(BinNode<T>* p, const T& val);
};
方式 2:顺序存储(仅完全二叉树适用)
概念
使用数组存放节点,依靠下标计算父子位置,无需指针:
下标 i 左孩子:2*i
下标 i 右孩子:2*i+1
下标 i 父节点:i/2
特性
普通二叉树使用顺序存储必须填充大量空占位节点,造成严重空间浪费;完全二叉树节点连续无空缺,无浪费。
拓展
堆(大顶堆 / 小顶堆)底层为完全二叉树,依赖数组实现,优先队列底层就是大顶堆。
2.4 堆(完全二叉树衍生结构)
概念
堆是满足特殊大小规则的完全二叉树,分为大顶堆、小顶堆。
-
大顶堆:任意节点值 ≥ 左右子节点,根为全局最大值;
-
小顶堆:任意节点值 ≤ 左右子节点,根为全局最小值。
应用
优先队列、堆排序、TOP-K 极值查找。
三、二叉树四大遍历方式
核心规则(递归统一逻辑)
前 / 中 / 后序遍历均以当前节点为区分基准:
-
前序:根 → 左 → 右
-
中序:左 → 根 → 右
-
后序:左 → 右 → 根
-
层序(广度遍历):从上到下、从左到右,非递归,依赖队列
3.1 前序遍历
递归实现
template<typename T>
void BinTree<T>::preRec(BinNode<T>* p){
if (p == nullptr) return;
cout << p->data << " "; // 访问根
preRec(p->left); // 递归左
preRec(p->right); // 递归右
}
非递归(stack)
思路:先压右、再压左,保证出栈顺序 根→左→右
template<typename T>
void BinTree<T>::preOrderNoRec(){
if (root == nullptr) return;
stack<BinNode<T>*> st;
st.push(root);
while (!st.empty()){
BinNode<T>* cur = st.top();
st.pop();
cout << cur->data << " ";
// 先压右,后压左
if (cur->right != nullptr)
st.push(cur->right);
if (cur->left != nullptr)
st.push(cur->left);
}
cout << "\n";
}
3.2 中序遍历
递归实现
template<typename T>
void BinTree<T>::inRec(BinNode<T>* p){
if (p == nullptr) return;
inRec(p->left); // 左
cout << p->data << " "; // 根
inRec(p->right); // 右
}
非递归(stack)
思路:一路向左压栈,无左则弹出访问,再走右子树
template<typename T>
void BinTree<T>::inOrderNoRec(){
stack<BinNode<T>*> st;
BinNode<T>* cur = root;
while (cur != nullptr || !st.empty()){
// 持续往左走,全部入栈
while (cur != nullptr){
st.push(cur);
cur = cur->left;
}
cur = st.top();
st.pop();
cout << cur->data << " ";
cur = cur->right; // 处理右子树
}
cout << "\n";
}
3.3 后序遍历
递归实现
template<typename T>
void BinTree<T>::postRec(BinNode<T>* p){
if (p == nullptr) return;
postRec(p->left); // 左
postRec(p->right); // 右
cout << p->data << " "; // 根
}
非递归(双stack)
思路:栈 1 存节点,栈 2 逆序保存输出,最终栈 2 弹出即为后序
template<typename T>
void BinTree<T>::postOrderNoRec(){
if (root == nullptr) return;
stack<BinNode<T>*> st1, st2;
st1.push(root);
while (!st1.empty()){
BinNode<T>* cur = st1.top();
st1.pop();
st2.push(cur);
// 先左后右入栈1,出栈后右先进st2
if (cur->left != nullptr)
st1.push(cur->left);
if (cur->right != nullptr)
st1.push(cur->right);
}
// st2弹出 左 右 根
while (!st2.empty()){
cout << st2.top()->data << " ";
st2.pop();
}
cout << "\n";
}
3.4 层序遍历
层序无标准递归写法,只能借助队列queue容器逐层遍历,逐层输出节点,BFS 广度思想 。
template<typename T>
void BinTree<T>::levelOrder(){
if (root == nullptr) return;
queue<BinNode<T>*> q;
q.push(root);
while (!q.empty()){
BinNode<T>* cur = q.front();
q.pop();
cout << cur->data << " ";
if (cur->left != nullptr)
q.push(cur->left);
if (cur->right != nullptr)
q.push(cur->right);
}
cout << "\n";
}
拓展考点
已知两种遍历序列可唯一还原二叉树;层序不能递归实现。
四、二叉树常用递归算法(求高 / 节点数 / 插入 / 删除)
4.1 求子树高度
概念
递归获取左右子树高度,取最大值 + 1 为当前节点高度;空节点高度 = 0。
代码
template<typename T>
int BinTree<T>::getHeight(BinNode<T>* p)
{
if(nullptr == p) return 0;
int leftH = getHeight(p->left);
int rightH = getHeight(p->right);
return leftH > rightH ? leftH + 1 : rightH + 1;
}
4.2 统计整棵树节点总数
概念
空节点返回 0,非空 = 1 + 左子节点数 + 右子节点数
template<typename T>
int BinTree::countNode(BinNode<T>* p)
{
if(!p) return 0;
return 1 + countNode(p->left) + countNode(p->right);
}
4.3 二叉搜索树插入(默认插入规则:小左大右)
概念
小于等于当前值放左子树,大于放右子树;递归找到空位置新建节点。
template<typename T>
void BinTree<T>::insert(BinNode<T>* p, const T& val)
{
if(val <= p->data)
{
if(p->left == nullptr)
{
p->left = new BinNode<T>(val);
p->left->parent = p;
}
else insert(p->left, val);
}
else
{
if(p->right == nullptr)
{
p->right = new BinNode<T>(val);
p->right->parent = p;
}
else insert(p->right, val);
}
}
4.4 节点删除(三大情况)
特性
-
叶节点(度 0):直接 delete,父对应指针置空;
-
度 1 节点:将唯一子树对接给父节点,释放自身;
-
度 2 节点:取前驱 / 后继节点替换数据后删除替代节点。
拓展
删除依赖父指针快速定位,因此手写节点建议保存 parent 指针。
五、拓展:四叉树 & 八叉树(游戏开发优化)
1. 四叉树(2D 场景)
概念
正方形地图不断均等四分,边长推荐 2 的幂次方(位运算加速);
用途
摄像机视锥裁剪,只渲染镜头内地块,远处低精度纹理,减少渲染面,提升帧率。
2. 八叉树(3D 场景)
概念
正方体空间沿 XYZ 三轴八等分;
用途
3D 场景遮挡剔除、AI 分区寻路、碰撞检测优化。
核心原理
树结构分层分区,跳过视野外物体渲染,大幅降低图形计算量,是游戏性能优化核心手段。