文字长度: ★★★☆☆
阅读难度: ★★☆☆☆
原创程度: ★★★★★
前言
树是一种数据结构,二叉树是一种特殊的树,二叉搜索树是一种特殊的二叉树。本文总结了二叉树/二叉搜索树中我认为最经典的一些题目,比较复杂的配了图来帮助理解。
目录-----加【】的是题目
- 树基本概念
- 深度
- 高度
- 层
- 叶子
- 二叉树
- 遍历序列
- 【前序遍历】iterative解法
- 【中序遍历】iterative解法
- 【后序遍历】iterative解法
- 【给中序+前序,还原二叉树】(1) recursive解法; (2) iterative解法
- 【给二叉树,求层次遍历序列】BFS解法
- 平衡树
- 【判断是否是平衡树】
- 后继结点/前驱结点
- 【求后继结点】(1) 知道parent; (2)不知道parent
- 遍历序列
- 二叉搜索树 (BST)
- 查询
- 【BST中查询某值】
- 【BST的最大/小值】
- 结点变换
- 【移植子树】
- 【BST中插入一个结点】
- 【BST中删除一个结点】(1) iterative解法; (2) recursive解法
- 构造
- 【从有序数组中构建平衡的BST】(1) recursive解法; (2) iterative解法
- 查询
1. 所有树都有的属性
【深度】对于某个结点而言,其深度(depth)就是从树的根结点到该结点的路径长度,即经过的结点数量(包括根结点但不包括自己),根结点的深度是0。孩子的深度=自己的深度+1。
相关考题: 求树中某个结点的深度
- 思路: DFS和BFS都可以
【高度】: 树中任意结点的深度的最大值,也即树中任意叶子的深度的最大值。
相关考题: 求树的高度
- 思路: DFS和BFS都可以
【层】: 所有拥有深度y的结点组成的集合称作树的level-y,根结点在level-0
【叶子】: 没有孩子结点的结点
2. 二叉树特有的属性名词
【遍历序列】 分为前序遍历序列,中序遍历序列,后序遍历序列,层次遍历序列。这部分典型的知识点包括: 写出
- 前序遍历 (Preorder Traversal) 不用递归的写法
vector
- 中序遍历 (Inorder Traversal) 不用递归的写法
vector
- 后序遍历 (Postorder Traversal) 不用递归的写法
vector
1. 给定前序+中序,还原二叉树(或者求后序,本质没差别)
- Recursive解法: 解题的核心要点是: preorder的第一个一定是根;inorder的根结点左边是左子树,右边是右子树。
- 时间复杂度: O(N^2)(对每个结点都要搜索,共n个结点)
- 空间复杂度: 除了递归栈,使用了O(1) 辅助空间
TreeNode
- Iterative解法: 这个解法比较优雅
- 时间复杂度: O(N)
- 空间复杂度: O(N)
- 将前序遍历的结点依次入栈,同时后一个结点作为前一个结点的左孩子,不断叠加。
- 如果前序遍历当前结点等于中序遍历的当前结点,出栈结点直到和中序遍历的结点不相等。并且同时设置flag为true,来标记做了一次pop操作。
- 重复上述过程直到前序遍历的栈为空。
- 核心要点是
- 只有preorder序列入栈操作时会在树中插入新结点
- flag为true时,插入新结点到上一个结点的右孩子位置,并且reset flag.
TreeNode
2. 给定后序+中序,还原二叉树
- 和前序+中序一样,只不过postorder的最后一个结点一定是根
给定二叉树,求层次遍历序列
- 经典的BFS解法
vector
【平衡树】任意节点的子树的高度差都小于等于1
相关考题: 判断一个给定的二叉树是否是平衡树
bool
【后继结点】(successor) 在给定二叉搜索树中,求某结点的中序遍历的后继结点
相关考题: 给定一个二叉搜索树,求某结点的中序遍历的后继结点的值
- 如果该结点有右孩子,那么就返回右孩子的最小结点( 时间复杂度O(logN) );否则返中序遍历中在自己后面并且最接近自己的祖先(找到结点的最近(层数差距最小)的祖先,使得该结点所在的子树是该祖先的左孩子) 。中序遍历中在自己后面并且最接近自己的祖先的求法分2种情况
- 第一种:二叉搜索树的结点有指向parent的指针,那么找到该祖先的方式是从该结点出发,向上搜索直到找到满足条件的祖先。
- 时间复杂度: O(logN)
- 第一种:二叉搜索树的结点有指向parent的指针,那么找到该祖先的方式是从该结点出发,向上搜索直到找到满足条件的祖先。
/**
- 第二种:二叉搜索树的结点没有指向parent的指针,那么找到该祖先的方式是从root出发,用一个指针curr去找该结点p,另一个指针succ用来记录当前值最小的祖先,如果遇到一个大于p的值的祖先,succ指针就指向之前的curr,curr指向它的左孩子。
- 时间复杂度: O(N)
TreeNode
【前驱结点】: 和“后继结点”相应,做对称操作即可
3. 二叉搜索树的基本操作
【查询】给定二叉搜索树中是否存在值为x的结点。查询是二叉搜索树最基本的操作。
TreeNode
- 变种题: 给定二叉搜索树中是否存在值为x的结点,如果存在,返回该结点到根的距离,如果不存在返回-1
TreeNode
【最大/小值查询】: 求给定二叉搜索树的最大/小值
TreeNode
【移植】将某个树v移植到树root的结点u处
TreeNode
【插入】在一个二叉搜索树中插入一个结点。注意,被插入的结点必然是一个“叶子”结点
TreeNode
【删除】: 在一个二叉搜索树中删除一个值为x的结点,如果该值不存在,则返回原来的树
删除一个结点比插入结点复杂很多,因为被删除的结点可能不是“叶子”结点。我们具体分为3种情况讨论:
- 如果被删除的结点只有左子树,那么就用p的左孩子移植到p的位置
- 如果被删除的结点只有右子树,那么就用p的右孩子移植到p的位置
- 如果被删除的结点有左右2个子树,那么需要找到右子树中最小的结点x,将它放到p的位置。由于x肯定没有左子树(否则不会是子树上最小的结点),但是可能有右子树。所以移动x到p之前,需要先把x从子树上删除。
// iterative 思路
- 另外还有一个recursive的思路:
- 如果当前结点就是需要被删除的结点
- 如果没有右孩子,就是把当前结点删除
- 如果有右孩子,将右子树上的最小结点和当前结点的值交换,再去删除右子树上的该值
- 如果当前结点的值大于需要被删除的值,那么被删除的值在左子树上
- 如果当前结点的值小于需要被删除的值,那么被删除的值在右子树上
- 如果当前结点就是需要被删除的结点
// recursive 思路
【构造】从一个有序数组,构建平衡的二叉搜索树
- recursive思路: 很直观,不需要多说
- 时间复杂度: O(N),显然每个结点对应调用了一次buildBST函数
- 空间复杂度: 即递归栈深度,O(logN)
TreeNode
- iterative: 由于从有序数组构建BST是一个深度优先遍历的问题,因此iterative的解法用栈来解决。
- 时间复杂度: O(N),显然是遍历了一遍数组
- 空间复杂度: 即压栈最深的深度,也即树的高度,O(logN)
TreeNode