二叉树理论基础篇
二叉树的题目分类
就是6种题型,4种二叉树的,6种二叉搜索书的
二叉树的种类
- 满二叉树
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树
若深度为k,则2^k-1个节点
- 完全二叉树
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点
优先级队列其实是一个堆,堆就是一棵完全二叉树,同时保证父子节点的顺序关系 - 二叉搜索树
二叉搜索树是有数值的了,二叉搜索树是一个有序树
定义如下: - 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
- 平衡二叉搜索树
平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
二叉树的存储方式
二叉树可以链式存储,也可以顺序存储
二叉树的遍历方式
深度优先遍历:先往深走,遇到叶子节点再往回走
广度优先遍历:一层一层的去遍历
前中后,其实指的就是中间节点的遍历顺序
栈和队列可以分别遍历深度优先遍历与广度优先遍历
二叉树的定义
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
题目
二叉树的递归遍历
递归方法论:
以下以前序遍历为例:
熟练的记住递归法的方法论,在递归的路上越走越稳!!!
144. 二叉树的前序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
ArrayList<Integer> Arr = new ArrayList<Integer>();
Traversal(root,Arr);
return Arr;
}
// 递归三要素
// 确定递归函数的参数和返回值
void Traversal(TreeNode root,ArrayList<Integer> Arr){
// 确定递归的结束条件
if(root == null) return;
// 确定单层递归逻辑
Arr.add(root.val);
Traversal(root.left,Arr);
Traversal(root.right,Arr);
}
}
145. 二叉树的后序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
ArrayList<Integer> Arr = new ArrayList<Integer>();
Traversal(root,Arr);
return Arr;
}
// 递归三要素
// 确定递归函数的参数和返回值
void Traversal(TreeNode root,ArrayList<Integer> Arr){
// 确定递归的结束条件
if(root == null) return;
// 确定单层递归逻辑
Traversal(root.left,Arr);
Traversal(root.right,Arr);
Arr.add(root.val);
}
}
94. 二叉树的中序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
ArrayList<Integer> Arr = new ArrayList<Integer>();
Traversal(root,Arr);
return Arr;
}
// 递归三要素
// 确定递归函数的参数和返回值
void Traversal(TreeNode root,ArrayList<Integer> Arr){
// 确定递归的结束条件
if(root == null) return;
// 确定单层递归逻辑
Traversal(root.left,Arr);
Arr.add(root.val);
Traversal(root.right,Arr);
}
}
二叉树的迭代遍历
为什么可以用迭代法(非递归的方式)来实现二叉树的前后中序遍历呢?
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因
前序遍历(迭代法)
前序遍历是中左右,每次先处理的是中间节点,那么**先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子**
// 迭代法
class Solution {
// 前序遍历顺序 中左右 入栈顺序:中--左----右
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
if(root==null) return result;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
if(node.right!=null) stack.push(node.right);
if(node.left!=null) stack.push(node.left);
}
return result;
}
}
中序遍历(迭代法)
处理顺序和访问顺序是不一致的
方法:在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素
// 迭代法
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
if(root==null) return result;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while(cur!=null||!stack.isEmpty()){
if(cur!=null){
stack.push(cur);
cur=cur.left;
}else{
cur=stack.pop();
result.add(cur.val);
cur=cur.right;
}
}
return result;
}
}
后序遍历(迭代法)
// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null){
return result;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
if (node.left != null){
stack.push(node.left);
}
if (node.right != null){
stack.push(node.right);
}
}
Collections.reverse(result);
return result;
}
}
二叉树层序遍历
102. 二叉树的层序遍历
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)
思路:
此题做过一次,但是还是做不出来,主要原因忽视了循环里面还应该有一个循环,用来规定这个每个数组的长度
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if(root==null) return res;
Queue<TreeNode> que = new LinkedList<>();
que.add(root);
while(!que.isEmpty()){
List<Integer> arr = new ArrayList<>();
int len =que.size();
while(len>0){
TreeNode tre = que.poll();
arr.add(tre.val);
if(tre.left!=null) que.add(tre.left);
if(tre.right!=null) que.add(tre.right);
len--;
}
res.add(arr);
}
return res;
}
}
学习:先把该建的数据结构,先建好,里面的循环画图,一步一步模拟实现
107. 二叉树的层序遍历 II
给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
思路:
先做一下层次遍历,然后把得到的二维数组,反转一下
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if(root == null) return res;
Queue<TreeNode> que = new LinkedList<>();
que.add(root);
while(!que.isEmpty()){
List<Integer> arr = new ArrayList<>();
int len = que.size();
while(len>0){
TreeNode tre = que.poll();
arr.add(tre.val);
if(tre.left!=null) que.add(tre.left);
if(tre.right!=null) que.add(tre.right);
len--;
}
res.add(arr);
}
List<List<Integer>> res2 = new ArrayList<>();
for(int i = res.size()-1;i>=0;i--){
res2.add(res.get(i));
}
return res2;
}
}
学习:思路是挺好想的,但是操作时出现了弱智错误是:先将右支放进了队列,然后又放的左支
199. 二叉树的右视图
此题就两个思路,思路1:在往数组里加的时候,确定是不是len=1,如果等于1,则加进去,不等于1,不加
思路2:先求出二维数组,然后求每个数组的最后一个
637.二叉树的层平均值
此题实际上和上一题类似,思路1:就是在如何加入数组的时候进行平均操作;
思路2:就是就是先求出二维数组,然后再求每个数组的平均值
用思路2左,做了1个半小时,才做出出来,遇到了好多弱智错误:
每一次都需要新建一维数组,每一次都需要,往外弹,这些语句应该放在那个循环内,需要重视
再就是每次循环完需要将平均值av调成0,不然把以前的数也加上了
最后要注意,root能不能为null,白和傻逼一样,直接判空
要学会调试,很多问题已调试就出来了
这样做时间复杂度8%特别低,效果不是很好
用思路1做,做了接近15分钟吧,出现的问题是:将len作为了求平均数的个数,导致数组li里面全是NAN,需要自己信息新增一个count进行计数,然后让他作为被除数
再就是要保证平均数为实数,所以需要将其强转,或者让一个类型成为实数类型,避免了强转
时间复杂度达到95%,这个方法效果好很多,而且也避免了新建很多动态数组
429. N 叉树的层序遍历
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
此题肯定是根据N叉树的定义做的,这样也不用判断左右树,直接用Children的子类,如果子类不等于空则进行加入到数组,应该是这样
此题大约做了20分钟,实际上改的就是如何往队列中加指针的问题,先求tre的孩子集合,先判断集合有没有问题,如果没有,就进行遍历求各个孩子,如果孩子没问题,就加进队列,就是这样的一个逻辑吧
集合的判断实际上有一个就行!!!
515. 在每个树行中找最大值
思路1:还是最简单的方法,就是求出二位数组,然后再求各一维数组的最大值,但此方法不是好方法
思路2:就是在往一维数组加元素时,需要和以前的判断,如果大就替换掉他,一直替换到最后一个,那就是最大的
思路1:做出来了,大约做了24分钟左右,出现的问题主要就是求最大值,设置最小值的API有点忘了,试了试试出来了,再就是出现的些弱智错误,都是每次报错后,根据报错的信息调对的
时间复杂度比较低,才9.05%
思路2:哎,真菜,什么错也出,调了接近30分钟,才挑出来,用的思路1的代码,结果每次都创建一个一维数组,产生了错误,研究了10几分钟,也没看出哪里错,这是奇葩,再就是当len=0的时候,才需要将max加入到arr中,而不是len =1
时间复杂度也不是很高,78%
116. 填充每个节点的下一个右侧节点指针
思路:
队列弹出值,值的指针指向队列的顶部
做了20分钟,没做出来,上面想的思路,不对,要保证len-1大于0才行,因为已经弹出1个元素,此时len为进行减1,需要在减1之后,队列中还有元素,才行!
为什么返回node而不是root呢?
node是最后一个元素,而root是第一个元素,后面连接着一大串
117.填充每个节点的下一个右侧节点指针II
与上一题的区别是非完美二叉树
104.二叉树的最大深度
- 思路: 先用层序遍历求二维数组,再求二维数组的大小,就是深度
- 思路: 记录一个深度数为0,len 不是大于0时,深度数 + 1
- 采用深度遍历应该也可以
111.二叉树的最小深度
思路: 当一节点的左右子树都为空时,返回当前深度,没有都为空的情况,则返回正常深度
226. 翻转二叉树
1.思路: 递归法: 到左节点,到右节点,然后进行交换即可【前后遍历】
2. 思路 层序遍历 直接得到二维数组,然后在进行一维数组的反转,并放进一维数组中
3. 思路: 不用队列,放进栈中应该也不错