树是常用的数据结构,应用较多的有二叉搜索树,二叉平衡搜索树(AVL树),红黑树,多路平衡搜索树(B-树,B+树,B*树)等。
# 基础
树可由链式结构或数组实现,遍历方式为前、中、后、层序遍历。
1 | |
# 树
有向无环非线性数据结构,每个节点可以指向多个节点,但至多有一个节点指向自身。
# 二叉树
每个节点至多只能指向两个节点。
# 满二叉树
只有度为0和2的节点,并且度为0的节点在同一层上。
# 完全二叉树
除了最底层节点可能没填满外,其余层节点数均达到最大值,并且最下面一层的节点应集中在最左侧。
# 二叉搜索树
一种朴素的有序树结构,左子节点均小于父节点,右子节点均大于父节点。当插入新值时由根节点开始按以上规则逐层比较,直到找到合适的叶节点位置。
- 优点:不需要在插入数据时调整结构
- 缺点:性能易受数据插入顺序影响,最差情况为顺序插入,退化为链表。
# 平衡二叉搜索树(AVL树)
二叉搜索树的改进,又称AVL(Adelson-Velsky and Landis)树,引入平衡因子,通过旋转操作保证所有子树高度差不超过1。
- 优点:相比二叉树更平衡,极端情况下表现依然足够好。
- 缺点:在读写比例差不多的情况下,旋转操作过多。
# 红黑树(RB树)
AVL树的改进,引入着色规则,通过旋转和着色操作保证所有子树高度差不超过2。Map可以通过红黑树实现。
- 优点:写性能更高。
- 缺点:查询性能弱于AVL树,因为子树高度差比AVL多一层,最差情况会多一次比较。
# B-树(B-Tree, B树)
多路搜索树,又称B树,用于数据索引,子节点内放指针和数据块。通过分裂操作维持树结构。
# B+树
B-树改进型,子节点内不放数据块,统一放在叶节点内。查询性能稳定,IO数据利用率高。
# B*树
B+树改进型,增加子节点间指针,如果兄弟节点数据未满,则将一部分指针移到兄弟节点,并修改父节点关键字,将结点的最低利用率从1/2提高到2/3。
# 堆
完全二叉树
插入:插入队尾,上浮,如果大于parent则交换
删除:删除堆顶元素,用队尾元素替换,下沉,如果小于子节点中较大的,则交换
替换:先删除再插入
堆化:将数组当作完全二叉树,依次对所有非叶子结点下沉
# 实践
# 二叉树的前序遍历
- 递归
1 | |
- 迭代
1 | |
# 二叉树的后序遍历
- 递归
1 | |
- 迭代
1 | |
# 二叉树的中序遍历
- 递归
1 | |
- 迭代
1 | |
C++ 版本
1 | |
# 二叉树的层序遍历
- 队列先进先出
1 | |
# 二叉树的层序遍历II
- 同二叉树的层序遍历
1 | |
# 层序遍历获取二叉树的右视图
- 同二叉树的层序遍历
1 | |
# 层序遍历获取二叉树的层平均值
- 同二叉树的层序遍历
1 | |
# 层序遍历获取N叉树的层序遍历
- 同二叉树的层序遍历
1 | |
# 前序遍历二叉树展开为链表
1 | |
# 翻转二叉树
- 递归
1 | |
- 迭代
1 | |
- 层序
1 | |
# 对称二叉树
- 递归
1 | |
- 迭代,队列和栈均可
1 | |
# 二叉树的最大深度
- 后序遍历
1 | |
- 层序遍历
# N叉树的最大深度
- 迭代,同二叉树
- 层序遍历
1 | |
# 二叉树的最小深度
- 递归
1 | |
- 层序遍历
1 | |
# 完全二叉树的节点数
- 前序遍历递归
1 | |
- 层序遍历迭代
1 | |
# 平衡二叉树
- 递归
1 | |
# 二叉树的所有路径
- 前序遍历递归
1 | |
# 相同的树
- 递归,同对称树
1 | |
- 迭代,队列和栈均可,同对称树
1 | |
# 左叶子之和
- 递归
1 | |
# 找树左下角的值
- 层序遍历
1 | |
# 路径总和
- 递归
1 | |
# 路径总和II
- 递归,注意Go的引用
1 | |
# 从中序与后序遍历序列构造二叉树
- 先取后序遍历最后一个值作为当前根节点,从中序遍历比对确定左右子树切割位置,后序遍历子树大小应和中序遍历数组子序列一致,将切分出的左右子序列递归。
1 | |
# 从中序与前序遍历序列构造二叉树
- 类似后序+中序
1 | |
- 改写成slice更简洁
1 | |
# 最大二叉树
- 递归
1 | |
# 合并二叉树
- 递归
1 | |
# 二叉搜索树中的搜索
- 递归
1 | |
- 迭代
1 | |
# 验证二叉搜索树
- 递归,注意子树区间必须,需要上下界来判断
1 | |
- 中序遍历,序列递增
1 | |
# 二叉搜索树的最小绝对差
- 中序遍历,Go的struct指针
1 | |
# 二叉搜索树中的众数
- 二叉搜索树中序遍历有序
1 | |
# 二叉树的最近公共祖先
- 递归
1 | |
# 二叉搜索树的最近公共祖先
- 二叉搜索树有序,前序遍历,第一个落在[p, q]或[q, p]区间的就是公共祖先
1 | |
- 迭代
1 | |
# 二叉搜索树中的插入操作
- 利用二叉搜索树的性质插入
1 | |
# 删除二叉搜索树中的节点
- 删除节点时分五种情况:
- 找不到
- 左右都为空,直接删除
- 左空右不空,等于右
- 右空左不空,等于左
- 左右都不空,有两种方案
- 用右子树中最小的节点替换,退化为2,因为右子树中最小节点也一定比左子树中的任意节点都大,这个节点是右子树的最左叶子节点。可以统一2/3/5。
- 把左子树挂在右子树的最左叶子节点,退化为3
1 | |
# 修剪二叉搜索树
- 前序遍历,如果节点值小于下界,左子树一定都小于下界,右子树不一定,可以先把右子树替换当前节点,递归判断。
1 | |
# 将有序数组转换为二叉搜索树
- 利用二叉搜索树有序特性,二分数组
1 | |
# 把二叉搜索树转换为累加树
- 反中序遍历累加即可
1 | |