1. 前言
- 来自广工考研真题
- 平衡二叉树是一种高级数据结构,是O(nlogn)搜索算法的基础数据结构
- 拆解题目内容: 后序遍历求树高度 + 全局计数器 + 节点平衡定义 + 返回值语义确认
- 实现方式:递归遍历 + 引用类型做全局计数器
2. 读题
- “判断” 属于二叉树的遍历
- “是否” 则是遍历中需要做的具体操作
- “平衡” 来自于定义:任意节点左右子树高度差小于2 =>
|左子树高度 - 右子树高度| < 2
3. 梳理思路
递归得让每个节点知道自己所在的高度,并向上报告,其双亲节点判断自己的左右子树是否平衡。任意子树不平衡,整棵树都不平衡。
-
为什么使用递归?
总问题可以分割成相同的子问题,子问题依次解决后总问题解决。契合递归的思想。 -
为什么使用后序遍历?
后续遍历是最后访问根节点,从叶子节点慢慢回溯到根节点,每个双亲节点都能知道左右孩子的信息。 -
为什么使用全局计数器?
树的高度
在递归回溯的过程中是单项递增的,如果方法签名定义一个引用类型&int i
,递归结束即可为i
赋值为整棵树的高度。 -
如何确定返回值?
“任意子树不平衡,整棵树都不平衡。”,返回值使用布尔类型
返回值一方面也是为了简化递归过程,也就是剪枝
的功能,剪枝
是个大话题,这里不表。
换言之,已经存在任意子树不平衡了,直接向上返回FALSE
就好,如下代码能做到的就是,左边子树出现了不平衡,后续省略右子树的判断。函数输出TRUE
则为平衡二叉树if ( ! f(T -> lchild) || ! f(T -> rchild) ) { return false; }
4. 实现
4.1 结构体定义
typedef struct BiTNode {
int data;
struct BitNode *lchild, *rchild;
} BiTNode, *BiTree;
4.2 使用引用类型记录高度,返回值作为剪枝依据(不可运行)
bool f(BiTree T, int &h) {
if (NULL == T) { // 1. 空子树, 高度为0, 一定平衡。
h = 0;
return TRUE;
}
int h1, h2; // 左右子树高度
if(T->lchild == NULL && T->rchild == NULL) {
h = 1;
return TRUE; // 2. 递归到叶子节点的空节点,高度为1,一定平衡。
}
if ( ! f(T->lchild, h1) || ! f(T->rchild, h2) ) {
// 递归出口,任意子树不平衡整个树不平衡,h这里对于算法无意义了,可以不赋值
return FALSE;
}
}
4.3 后序遍历求树的高度
可以作为独立的代码实现,也可以作为4.4 的编码依据
int f(BiTree T) {
if (T == null) return 0;
int leftHight = f(T->lchild);
int rightHight = f(T -> rchild);
return 1 + (leftHight > rightHight ? leftHight : rightHight ); // 双亲节点的高度 = 左右子树高度最大值 + 1
}
4.4 加入判断是否平横的逻辑完整代码 (可运行)
bool f(BiTree T, int &h) {
if (NULL == T) { // 1. 空子树, 高度为0, 一定平衡。
h = 0;
return TRUE;
}
int h1, h2; // 左右子树高度
if(T->lchild == NULL && T->rchild == NULL) {
h = 1;
return TRUE; // 2. 递归到叶子节点的空节点,高度为1,一定平衡。
}
if ( ! f(T->lchild, h1) || ! f(T-> rchild, h2) ) {
// 递归出口,任意子树不平衡整个树不平衡,h这里对于算法无意义了,可以不赋值
return FALSE;
}
// 记录回溯到该双亲节点时的高度
h = 1 + (h1 > h2 ? h1 : h2);
// 当前节点作为双亲节点,根据左右节点高度判断是否不平衡。 abs() 取绝对值
return (abs(h1 - h2)) < 2;
}