今日收获:二叉树的理论基础,三种遍历方式(递归遍历,迭代遍历,层序遍历)
1. 二叉树的理论基础
(1)不同的树:排序树就是搜索树
二叉排序树:左节点的值<根节点<右节点的值,子树也是如此
平衡二叉排序树:除了满足排序树要求之外,左右子树的高度差小于等于1
(2)Java中二叉树节点的定义
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;
}
}
2. 递归遍历
(1)递归三要素:返回值和参数,确定递归的终止条件,单层递归的逻辑
(2)前序递归遍历:144. - 力扣(LeetCode)
class Solution {
List<Integer> result = new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {
preOrder(root);
return result;
}
// 返回值为空,传入遍历节点
public void preOrder(TreeNode root){
// 终止条件
if (root==null){
return;
}
// 单层递归
result.add(root.val);
preOrder(root.left);
preOrder(root.right);
}
}
(3)中序递归遍历和后序递归遍历:单层递归逻辑中改变递归左右子树与处理当前节点的顺序
中序递归遍历:94. - 力扣(LeetCode)
后序递归遍历:145. - 力扣(LeetCode)
3. 迭代遍历
(1)前序迭代遍历:144. - 力扣(LeetCode)
思路:利用栈存储当前节点及其左右孩子节点。当栈不为空时,弹出栈顶元素并记录值,先将右节点入栈,再让左节点入栈,这样出栈顺序就是中左右。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack=new Stack<>();
// 当前节点入栈,入栈节点必不为空
if (root==null){
return result;
}
stack.push(root);
while (!stack.isEmpty()){
TreeNode cur=stack.pop();
result.add(cur.val);
// 右孩子和左孩子入栈
if (cur.right!=null){
stack.push(cur.right);
}
if (cur.left!=null){
stack.push(cur.left);
}
}
return result;
}
}
(2)中序迭代遍历:94. - 力扣(LeetCode)
思路:不断向栈中压入当前节点和当前节点的左孩子,直到当前节点为空时弹出栈顶元素并记录值,然后将指针指向当前节点的右孩子,进入下一轮循环
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
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;
}
}
(3)后序迭代遍历:145. - 力扣(LeetCode)
思路:和前序遍历的左右子树入栈顺序相反,最后将结果数据反转。入栈顺序中左右,出栈是中右左,再反转就是左右中
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack=new Stack<>();
if (root==null){
return result;
}
stack.push(root);
// 和前序迭代遍历左右孩子入栈顺序相反
while (!stack.isEmpty()){
TreeNode temp=stack.pop();
result.add(temp.val);
// 左右入栈
if (temp.left!=null){
stack.push(temp.left);
}
if (temp.right!=null){
stack.push(temp.right);
}
}
// 出栈顺序中右左反转
Collections.reverse(result);
return result;
}
}
(4)迭代遍历统一写法
思路:三种遍历顺序按照和出栈相反的顺序入栈。在压入中节点时,额外压入一个空节点作为标记
前序统一迭代遍历:144. - 力扣(LeetCode)
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack=new Stack<>();
// 当前节点入栈,入栈节点必不为空
if (root!=null){
stack.push(root);
}
while (!stack.isEmpty()){
TreeNode temp=stack.peek();
if (temp!=null){
stack.pop(); // 避免重复,下面要统一处理
// 不是中节点
if (temp.right!=null){
stack.push(temp.right);
}
if (temp.left!=null){
stack.push(temp.left);
}
stack.push(temp);
stack.push(null);
}else {
// 遇到了中节点
stack.pop(); // null
TreeNode cur=stack.pop();
result.add(cur.val);
}
}
return result;
}
}
中序统一迭代遍历:94. - 力扣(LeetCode)
后序统一迭代遍历:145. - 力扣(LeetCode)
4. 层序遍历:做一送十,关键是标记遍历的层级
题目连接:102. - 力扣(LeetCode)
递归思路:利用整数标记遍历的层数,左右子树传入相同的层级数
class Solution {
List<List<Integer>> result=new ArrayList<>();
public List<List<Integer>> levelOrder(TreeNode root) {
sameLevel(root,0);
return result;
}
public void sameLevel(TreeNode node,int deep){
if (node==null){
return;
}
deep++;
// 新的一层列表
if (result.size()<deep){
List<Integer> item=new ArrayList<>();
result.add(item);
}
// 当前节点加入所在列表
result.get(deep-1).add(node.val);
sameLevel(node.left,deep);
sameLevel(node.right,deep);
}
}
迭代思路:利用队列,首先将根节点加入队列,如果队列的大小不为空,首先记录本层级的元素个数,再不断弹出队头元素并加入其左右节点,本层级遍历结束再记录下一层级的元素个数。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result=new ArrayList<>();
Queue<TreeNode> queue=new LinkedList<>();
if (root!=null){
queue.offer(root);
}
while (!queue.isEmpty()){
// 本层级的长度
int len=queue.size();
List<Integer> item=new ArrayList<>();
while (len>0){
TreeNode temp=queue.poll();
// 左右节点入队
if (temp.left!=null){
queue.offer(temp.left);
}
if (temp.right!=null){
queue.offer(temp.right);
}
item.add(temp.val);
len--;
}
result.add(item);
}
return result;
}
}
思路:反转层序遍历的结果列表
思路:每次在结果集中添加层序遍历中每层的最后一个元素
思路:层序遍历计算每一层的元素之和再求平均值
思路:二叉树的左右子树入队变为子树列表入队
思路:记录每一层的最大值
思路:层序迭代遍历,只要本层还有元素,就将弹出的元素指向队头元素
同116的做法
思路:层序迭代遍历记录总的循环次数
思路:层序迭代遍历,找出层中的第一个叶子节点就返回深度
5. 大总结
(1)每一种遍历方式的递归法和迭代法必须都掌握,尤其是迭代法。
(2)代码随想录中总结的递归三部曲:返回值和参数,终止条件,单层递归的逻辑