一、二叉树的遍历
1.1、二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
//BFS:广度优先搜索
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();//用队列来操作
if(root==null) {
return new ArrayList<>();//因为返回值类型是List
}
//第一层:根节点入队列
queue.offer(root);
List<List<Integer>> res = new ArrayList<>();//存放所有层的数据
while(!queue.isEmpty()){
int thisLevel = queue.size();//这一层的结点数,用于判断该层是否循环结束
List<Integer> list = new ArrayList<>();//用于存放当前层数据
for(int i=0;i<thisLevel;i++){//遍历这一层
TreeNode cur = queue.poll();//出队列,并作为父结点,也是当前层数据
list.add(cur.val);//将当前层数据放进去
//下一层的数据入队列,作为下一层循环新的父结点
if(cur.left!=null){
queue.add(cur.left);//如果左孩子不为空,就放入队列中
}
if(cur.right!=null){
queue.add(cur.right);
}
}
res.add(list);//当前层结束后,将当前层放入List,重新开始for循环
}
return res;
}
}
总结:
1.用LinkedList来构建队列queue,用于辅助操作,每次入队一层,先将根结点入队;
2.构建ArrayList存放所有层的数据;
3.构建ArrayList存放一层的数据;
4.将队列中的队首出队,作为父结点
1.2 二叉树的锯齿形层次遍历(蛇形遍历)
——LeetCode 103
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
if (root == null) {
return new ArrayList<List<Integer>>();
}
List<List<Integer>> results = new ArrayList<List<Integer>>();
DFS(root, 0, results);
return results;
}
public void DFS(TreeNode node, int level, List<List<Integer>> results) {
if (level >= results.size()) {
LinkedList<Integer> newLevel = new LinkedList<Integer>();
newLevel.add(node.val);
results.add(newLevel);
} else {
if (level % 2 == 0)
results.get(level).add(node.val);
else
results.get(level).add(0, node.val);
}
if (node.left != null) DFS(node.left, level + 1, results);
if (node.right != null) DFS(node.right, level + 1, results);
}
}
1.3、二叉树的前、中、后序遍历
1.3.1 递归方式解决
【1】前序遍历:打印-左-右
【2】中序遍历:左-打印-右
【3】后序遍历:左-右-打印
【理解递归】 递归一次就开辟一个栈帧,当递归到满足边界条件,进行return时,就会原路返回到调用该递归的地方,进行操作。
可参考本人之前的文章:链接
以中序遍历为例:
给定一个二叉树,返回它的中序遍历
递归方式:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
if(root==null){
return new ArrayList<>();
}
List<Integer> list = new ArrayList<>();
dfs(root,list);
return list;
}
public void dfs(TreeNode root,List<Integer> list){
if(root==null){
return;
}
dfs(root.left,list);
list.add(root.val);
dfs(root.right,list);
//前序遍历和后序遍历只是这三行调换位置!
}
}
—————————————————————————————————————————————
核心整理
【理解】中序遍历:3 - 2 - 4 - 1 - 5
【中序遍历具体步骤】 :先一路左递归至null,此时直接return,返回到前面调用dfs()方法的地方,即root.val=3,加入到list中,然后dfs(root.right,list)第一次即return;继续返回至上一次调用它的地方,即root.val = 2,进行后续操作:list.add(),dfs(root.right,list),此时会返回右叶子结点4,直至return后,返回到2,继续到上一次调用的地方:root.val = 1,。。。依次类推。
public void dfs(TreeNode root){
if(root == null) return;
dfs(root.left);
System.out.println(root.val);
dfs(root.right);
}
【后序遍历具体步骤】:还是以上图为例,后序遍历为3-4-2-5-1
——先一路访问左孩子,左递归到null为止,然后按左递归的原路返回到结点3处,执行下面的语句,右递归发现为null,返回上一个结点,打印3;再下一轮继续按左递归原路返回到2结点,执行执行dfs(root.right),到4,左递归为null,有递归也会null,返回到4,打印4,之后回到2,执行dfs(root.right)之后的语句,打印2;再下一轮,回退到1,访问右结点5,其左递归为null,右递归为5,发现5的左右都为null,返回,打印5,返回,打印1.
递归依次就开辟一个栈帧,可见递归法占用的空间较大。
如果是直接打印初步,则无需按照题目的固定方法格式,一般手撕代码可以写为:
//后续遍历:简单输出版
public void postTraversal(TreeNode root){
if (root == null){
return;
}
postTraversal(root.right);
postTraversal(root.left);
System.out.print(root.data + " ");
}
1.3.2 非递归方式
一、前序遍历:
lass Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();//存放结果的数组
if(root == null){
return list;
}
Stack<TreeNode> stack = new Stack<>();//借助栈来调节输出顺序
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.pop();
list.add(node.val);
if(node.right != null){
stack.push(node.right);
}
if(node.left != null){
stack.push(node.left);
}
}
return list;
}
}
二、中序遍历:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode curr = root;
while(curr != null || !stack.isEmpty()){
//将左子树逐个入栈
if(curr != null){
stack.push(curr);
curr = curr.left;
}else{//即到达了树的最底层之下,curr=null
curr = stack.pop();//栈顶的即为中序遍历第一个节点
list.add(curr.val);//节点数值直接入队列
curr = curr.right;//若还未到达父结点,则curr.right=null,继续入队列
}
}
return list;
}
}
三、后序遍历
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null){
return list;
}
Stack<TreeNode> stack1 = new Stack<>();//存放结点
Stack<Integer> stack2 = new Stack<>();//存放数值
stack1.push(root);
while(!stack1.isEmpty()){
TreeNode tmp = stack1.pop();
if(tmp != null){
stack2.push(tmp.val);
stack1.push(tmp.left);
stack1.push(tmp.right);
}
}
while(!stack2.isEmpty()){
list.add(stack2.pop());
}
return list;
}
}
二、二叉树的深度与高度计算
2.1、求二叉树深度
2.1.1 递归法
//递归方式
public int deepTree(TreeNode root){
if(root==null) return 0;
return Math.max(deepTree(root.left),deepTree(root.right))+1;
}
求各个结点的左右子树最大深度+1。
先递归root的左子树到9,递归9的左右子树均为0,向上返回,max(0,0)+1,root左子树深度为1;
再递归root右子树,递归2的左子树,递归1,到null,返回2的子树1的深度max(0,0)+1,递归2的右子树,直到null,返回max(0,0)+1,再向上返回子树2的深度max(1,1)+1=2,返回3的深度max(1,2)+1=3——总结:每个结点都去返回一次,从底部向上,返回值作为下一次返回比较的判断左右子树最大值的依据。
2.1.2 层序遍历法
public int deepTree(TreeNode root){
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int count = 0;//当前层已遍历的节点数
int levelCount = 1;//当前层结点总数
int depth = 0;//当前深度
while(!queue.isEmpty()){
root = queue.poll;
count++;
if(root.left!=null){
queue.offer(root.left);
}
if(root.right!=null){
queue.offer(root.right);
}
if(count==levelCount){
count = 0;
levelCount = queue.size();
depth++;
}
}
return depth;
}
2.2 求二叉树最小深度
- 深度是指:根节点到叶子结点的路径上结点数量
- 除非是只有一个根节点,不然的话必须要有一个根节点跟叶子节点才能组成路径。根节点自己不能作为叶子节点,所以[1,2]最小深度2,[1]最小深度1.
class Solution {
public int minDepth(TreeNode root) {
if(root == null){
return 0;
}
//如果根节点的左或右子树为空的话是构不成子节点的!
if(root.left == null && root.right != null){
return 1 + minDepth(root.right);
}
if(root.left != null && root.right == null){
return 1 + minDepth(root.left);
}
return 1+Math.min(minDepth(root.left),minDepth(root.right));
}
}
2.3、求二叉树高度
public int maxDepth(TreeNode root){
if(root==null)
return 0;
int L = maxDepth(root.left);
int R = maxDepth(root.right);
return Math.max(L,R) + 1;
}
三、二叉树的直径
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
//可求左右子树最大深度,再相加-1
class Solution {
int max=0;
public int diameterOfBinaryTree(TreeNode root) {
max = 1;
dfs(root);
return max-1;
}
public int dfs(TreeNode root){//求深度
if(root==null) return 0;
int L = dfs(root.left);//以该节点左子树为根的子树深度
int R = dfs(root.right);//以该节点右子树为根的子树深度
max = Math.max(max,L+R+1);L+R+1为该结点的直径
return Math.max(L,R)+1;//以该节点为根的子树的最大深度
}
}
【扩展题】二叉树展开为链表
【思路】:先前序遍历,并将结果放入动态数组ArrayList中,
//先前序遍历,放入List动态数组中,再遍历数组,形成单链表
//按照题意,展开后还是一个二叉树形式!
class Solution {
public void flatten(TreeNode root) {
if(root == null){
return;
}
List<TreeNode> list = new ArrayList<>();
dfs(root,list);//前序遍历
//前序遍历数组第一个值为单链表的首元节点head
TreeNode head = list.get(0);//先设置到head,便于后面操作
head.left = null;
for(int i=1;i<list.size();i++){
TreeNode tmp = list.get(i);
tmp.left = null;
head.right = tmp;
head = head.right;
}
}
//前序遍历。结果为把结点放入动态数组
public void dfs(TreeNode root,List<TreeNode> list){
if(root == null){
return;
}
list.add(root);
dfs(root.left,list);
dfs(root.right,list);
}
}