学习目标:
1.如何定义二叉树
2.利用递归遍历二叉树(dfs)
3.利用非递归(栈)的解法遍历二叉树
4.统一迭代(中前后序统一起来)
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;
}
}
二叉树的定义和链表是差不多的,相对于链表 ,二叉树的节点里多了一个指针, 有两个指针,指向左右孩子。
2.利用递归遍历二叉树(dfs)
运用递归的思想: 1.确定好循环结束的点——当指针指向空的时候 2. 确定好返回值,这里是void方法那就是加入到res集合中。3.确定好逻辑,前序遍历那就是先处理中子树,前后序的思想是一样的这里就不说了
/**
* 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 {
static List<Integer> res;
public List<Integer> preorderTraversal(TreeNode root) {
res = new ArrayList<>();
dfs(root);
return res;
}
public static void dfs(TreeNode root) {
if (root == null)
return;
res.add(root.val);
dfs(root.left);
dfs(root.right);
}
}
3.利用非递归(栈)的解法遍历二叉树
注意:这里的前序后序遍历的方式和后序不同。
1.前序、后序遍历二叉树
原理:利用栈,把中节点放进栈里面然后弹出来,如果弹出来的节点的左右节点不为空的话再放回栈中,下一次循环弹出。基本原理是这样,但是有一些细节需要注意,比如 1.我在实现代码的过程中忘记先判断(root == null ?)如果不这样的话后面调用val的函数可能会报空指针异常。2.在压入栈的过程中,如果是前序遍历,要先放右子树,再放左子树,虽然前序遍历是中左右,但是入栈顺序得先是右孩子,这样出栈顺序才和前序遍历的顺序一样。
这是前序遍历的实现代码:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null){
return res;
}
Stack<TreeNode> st = new Stack<>();
st.push(root);
while(!st.isEmpty()){
TreeNode temp = st.pop();
res.add(temp.val);
if(temp.right != null) {
st.push(temp.right);
}
if(temp.left != null) st.push(temp.left);
}
return res;
}
}
后序遍历:其实将前序遍历的放入顺序变成先左再右,这样出栈顺序变成了中右左,这样把出栈后的集合倒过来不就是后序了(左右中)?
后序遍历的实现代码:最后用了reverse的翻转集合
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null){
return res;
}
Stack<TreeNode> st = new Stack<>();
st.push(root);
while(!st.isEmpty()){
TreeNode temp = st.pop();
res.add(temp.val);
if(temp.left != null) st.push(temp.left);
if(temp.right != null) {
st.push(temp.right);
}
}
Collections.reverse(res);
return res;
}
}
后序遍历的实现较前两者麻烦:
因为前序遍历中访问节点(遍历节点)和处理节点(将元素放进result数组中)可以同步处理,但是中序就无法做到同步!
思路:先定义一个指针指向根节点,然后先是左孩子,指针往左移,直到左孩子为空的时候就要弹出来纪录到res中,这时候指针指向弹出来的节点的右孩子继续进行下一次循环,如果右孩子为空,就从栈中弹出下一个,若不为空,则一样寻找左节点到空为止全部加入栈中。
不好理解的时候观看视频中序迭代遍历思路
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> st = new Stack<>();
TreeNode cur = root;
while(cur != null || !st.isEmpty()){//此时cur可能需要再往栈里加元素,所以终止条件不止栈为空
if(cur != null){
st.push(cur);
cur = cur.left;//循环所有左子树
}else{
TreeNode temp = st.pop();
res.add(temp.val);
cur = temp.right;
}
}
return res;
}
}
4.统一迭代(中前后序统一起来)
目前理解起来有点困难,后面有机会再理解
现在先利用代码的规律来写前后中序遍历
首先这是前序遍历的模板:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new LinkedList<>();
Stack<TreeNode> st = new Stack<>();
if (root != null) st.push(root);
while (!st.empty()) {
TreeNode node = st.peek();
if (node != null) {
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
st.push(node); // 添加中节点
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
} else { // 只有遇到空节点的时候,才将下一个节点放进结果集
st.pop(); // 将空节点弹出
node = st.peek(); // 重新取出栈中元素
st.pop();
result.add(node.val); // 加入到结果集
}
}
return result;
}
}
也就中间的代码不同
前序的是中左右,然后反过来,先放入右孩子,再放入左孩子,最后把放入中子的步骤换成放入一个null。
//前序
st.pop();
if (node.right!=null) st.push(node.right);
if (node.left!=null) st.push(node.left);
st.push(null);
中序,后序也一样:
//中序
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
st.push(node); // 添加中节点
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
//后序
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈
st.push(node); // 添加中节点
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)