基本概念
根节点、叶节点、内部节点;(真)祖先、(真)后代、父亲、孩子;度、深度、高度
- 根节点深度为0
- 单节点数高度为0,空树高度为-1
- 设二叉树有n个节点,n_i个度为i的节点,e条边
- 除去根节点,每个节点和其父节点间有一条边:e = n - 1
- 按度的定义:
- 度为0比度为2的节点恰好多1个
- 含n个节点的二叉树有Catalan(n)种
真二叉树、完全二叉树、满二叉树
- 深度为k的节点至多2^k个
- 高度为h的树节点树介于h+1到2^{h+1}-1间
- 含n个节点的二叉树的最小高度为⌊log_2n⌋,该高度恰好取得时二叉树为完全二叉树
- 含n个叶节点的真二叉树有Catalan(n-1)种
多叉树转换为二叉树:长子-兄弟表示法
- 每个节点维护指向第一个孩子(长子)和右兄弟的指针
基本动作
插入子树,删除子树
同List,记得更新高度
遍历
深度优先遍历
先序遍历
//从当前节点出发,沿左分支不断深入,直至没有左分支的节点;沿途节点遇到后立即访问
template <typename T, typename VST> //元素类型、操作器
static void visitAlongLeftBranch (BinNodePosi(T) x, VST& visit, Stack<BinNodePosi(T)>& S)
{
while (x)
{
visit(x->data); //访问当前节点
//可优化:通过判断,避免空的右孩子入栈(多一次判断,少一次push一次pop)
S.push(x->rc); //右孩子入栈暂存
x = x->lc; //沿左分支深入一层
}
}
template <typename T, typename VST> //元素类型、操作器
void travPre_I2 (BinNodePosi(T) x, VST& visit)
{
Stack<BinNodePosi(T)> S; //辅助栈
while (true)
{
visitAlongLeftBranch(x, visit, S); //从当前节点出发,逐批访问
if (S.empty()) //直到S空
{
break;
}
x = S.pop(); //弹出下一批的起点
}
}
中序遍历
递归遍历
用栈实现的迭代
栈存放还没来得及访问的子树根节点
用直接后继实现的迭代
直接后继:某节点在中序遍历序列中的下一位
- 若有右孩子,那么直接后继是右子树中最靠左下的节点
- 否则,直接后继是将当前节点包含于其左子树中的最低祖先
如果返回NULL,表明代码中倒数第二步的s为根节点,这表明节点v是最靠右下的节点,无直接后继。
记录回溯状态的就地迭代
后序遍历
增强序列
将NULL节点换为外部节点,视二叉树为真二叉树
遇到NULL节点输出固定字符(比如^)
视NULL节点为哨兵节点,由二叉树节点结论有:NULL节点恰比非NULL节点多一个
广度优先遍历(层次遍历)
先上后下、先左后右
- 辅助队列最大规模为上取整的n / 2
- 当二叉树有奇数个节点时
- 此时是真二叉树
- 层次遍历进行到任何非叶节点都会发生出1入2
- 最大规模出现在最后一个2度节点访问完成时
- 此时队列中有且仅有所有的叶节点
- 最大规模只会出现1次
- 当二叉树有偶数个节点时
- 此时不是真二叉树,有一个1度节点
- 层次遍历进行到该1度节点队列会发生出1入1
- 最大规模会出现2次
- 当二叉树有奇数个节点时
先序遍历序列+后序遍历序列可以确定广度优先遍历序列
易见先序遍历序列第一个节点和后序遍历序列倒数第一个节点均为根节点A
- 如果先序遍历序列第二个节点B和后序遍历序列倒数第二个节点C不同,这表明根节点A有两个孩子B、C,且B是A的左孩子,C是A的右孩子
- 以B、C剖分两序列,即得以B为根的子树和以C为根的子树的遍历序列
- 如果先序遍历序列第二个节点和后序遍历序列倒数第二个节点相同(均为B),这表明根节点只有一个孩子B(但不知道是左孩子还是右孩子)
- 两序列剩下的部分即为以B为根的子树的遍历序列
- 总之,确定了根节点A、下一层节点和下一层节点各自的先序、后序遍历序列
- 如此反复即得广度优先遍历序列
Ex. 某二叉树先序遍历序列是124536,后序遍历序列是452631,求广度优先遍历序列。
- 显然1是根节点
- 在1前后的深度均为1,2、3深度都是1
- 根据先序序列,4、6的深度是3,并且4在6左边
- 根据后序序列,5的深度是3,并且5在4,6中间
广度优先遍历序列为123456
后序遍历序列+广度优先遍历序列可以确定先序遍历序列
先序遍历序列+广度优先遍历序列不能确定后序遍历序列(广度和先序的顺序有重合,信息损失)
遍历序列重构二叉树
- 真二叉树的先序和后序遍历序列能唯一重构原树
可以确定节点x的左右子树的根节点l、r,采用分治法解。
- 增强先序或后序遍历序列均能唯一重构原树
- 增强中序遍历序列不能唯一重构原树
前缀无歧义编码PFC
- 不同字符的编码互不为前缀
- 字符编码不一定等长
最优编码树
使得(带权)平均编码深度最小
最优编码树必存在,但不一定唯一
- 双子性:最优二叉编码树必为真二叉树
- 如果有1度节点,表明编码空间存在浪费,直接删除即更短
- 层次性:叶节点深度之差不超过1,即只能出现在倒数两层以内
- 参考AVL的调整
Huffman编码
策略:Greedy
- 找到权重最低的两个(超)字符
- 合并,权重取权重之和
- 迭代
- Huffman树一定是最优编码树
- 最优编码树不一定都能通过Huffman树构造出来
- 考虑一个满的Huffman树,底层节点的任何全排列都可给出一棵最优PFC