1. 概述
- 树是n个结点的有限集
- 有且仅有一个特定的根,称为根结点
- 当$n > 1$时,其余结点可分为m个互不相交的有限集,其中每一个集合本身又是一棵树,称为子树
- 结点拥有的子树称之为度,度为0的节点称为叶子结点
- 结点的层次是从根开始定义起的,根为第一层,根的孩子为第二层
- 树中结点最大的层次称为深度
- 森林是m棵互不相交的数的集合
2. 二叉树
- 每个结点至多只有两棵子树,有左右之分,其次序不能随意颠倒
- 性质
- 在二叉树的第i层上至多有$2^{i-1}$个结点($i\geq 1$)
- 深度为k的二叉树至多有$2^k - 1$个结点($k \geq 1$)
- 对任何一颗二叉树T,如果其终端结点数为n,度为2的结点数为$n_2$,那么$n = n_2 + 1$
- 具有n个结点的完全二叉树的深度为$[\log_2 n] + 1$
- 如果对一棵有n个结点的完全二叉树
3. 二叉树代码实现
- BinaryTree.hpp
1 | // |
- BinaryTree.cpp
1 | // |
4. 平衡二叉树
- 左右子树的高度相差不超过 1 的树为平衡二叉树
- AVL树的查找、插入、删除操作在平均和最坏的情况下都是O(logn)
- 最小失衡子树
- 在新插入的结点向上查找,以第一个平衡因子的绝对值超过 1 的结点为根的子树称为最小不平衡子树。也就是说,一棵失衡的树,是有可能有多棵子树同时失衡的。而这个时候,我们只要调整最小的不平衡子树,就能够将不平衡的树调整为平衡的树
- 平衡二叉树的失衡调整主要是通过旋转最小失衡子树来实现的
- 旋转的目的就是减少高度,通过降低整棵树的高度来平衡。哪边的树高,就把那边的树向上旋转
- 左旋
- 节点的右孩子替代此节点位置
- 右孩子的左子树变为该节点的右子树
- 节点本身变为右孩子的左子树
- 右旋
- 节点的左孩子代表此节点
- 节点的左孩子的右子树变为节点的左子树
- 将此节点作为左孩子节点的右子树
4.1 插入操作
- 假设一颗 AVL 树的某个节点为 A,有四种操作会使 A 的左右子树高度差大于 1,从而破坏了原有 AVL 树的平衡性。平衡二叉树插入节点的情况分为以下四种
- 在A的左子树根节点的左子树上插入节点而破坏平衡,需要右旋
- 在A的右子树根节点的右子树上插入节点而破坏平衡,需要左旋
- 在A的左子树根节点的右子树上插入节点而破坏平衡,需要先左旋后右旋
- 在A的右子树根节点的左子树上插入节点而破坏平衡,需要先右旋后左旋
4.2 删除操作
4.3 代码实现
- AVLTree.hpp
1 | // |
- AVLTree.cpp
1 | // |
5. 面试题
5.1 二叉树反转
- 递归算法
- 交换根节点的左右子树
- 对左右子树分别执行递归反转
- 非递归算法
- 交换根节点的左右子节点
- 交换第二层每个节点的左右子节点
1 | TreeNode* invertTree(TreeNode* root) { |
1 | TreeNode* invertTree2(TreeNode* root) { |
5.2 深度优先遍历
- 从根节点出发,沿着左子树方向进行纵向遍历,直到找到叶子节点为止。然后回溯到前一个节点,进行右子树节点的遍历,直到遍历完所有可达节点为止
- 父节点入栈,父节点出栈,先右子节点入栈,后左子节点入栈。递归遍历全部节点即可
1 |
|
5.3 广度优先遍历
- 从根节点出发,在横向遍历二叉树层段节点的基础上纵向遍历二叉树的层次
- 父节点入队,父节点出队列,先左子节点入队,后右子节点入队。递归遍历全部节点即可
1 | void breadthFirstSearch(Tree root){ |