二叉树遍历方法
Preorder 前序遍历——根左(子树)右(子树)
Inorder中序遍历——左(子树)根右(子树)
Postorder后序遍历——左(子树)右(子树)根
递归三要素:
1、递归的定义: 递归这个函数干了一件什么事情,一句话描述清楚,确定返回值、传入参数、调用
2、递归的出口: 什么时候是结束递归,退出条件是什么
3、递归的拆解: 将问题拆分为更小的问题,将数据规模变小,拆分成更小的子问题
遍历与分治法的区别是:
1、返回值不同 遍历返回值通常是void 分治法可有返回值
2、策略不同 遍历相当于整体执行,分治法相当于分成多个部分再合成一个整体,分而治之。(分治法 多个部分可以并发执行)
思路: 碰到二叉树的问题,就想想整棵树在该问题上的结果和左右子树在该问题上的结果之间 的联系是什么
前序遍历
leetcode 144题 144. 二叉树的前序遍历 - 力扣(LeetCode) (leetcode-cn.com)
/**
\* 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) {
List<Integer> result = new ArrayList<Integer>();
preorder(root,result);
return result;
}
// 递归的定义:将root为根的preorder加入到result里面
public void preorder(TreeNode root , List<Integer> result){
//递归的出口:什么时候结束递归 结束条件 不能无限调用
if(root == null){
return;
}
//递归的拆解 一维的完成步骤,根 左子树 右子树
result.add(root.val);
preorder(root.left, result);
preorder(root.right, result);
}
}
时间复杂度:O(n) 每个结点恰好被遍历一次
空间复杂度:O(n) 递归过程中栈的开销 平均情况下为O(logn),最坏情况下为O(n);
//方法二:非递归 特点:出栈时访问 入栈的顺序:根 左 右,而栈的后进先出的,所以先让右孩子入栈(后访问),再让左孩子入栈
public List<Integer> preorderTraversal(TreeNode root){
// 创建栈
Stack<TreeNode> stack = new Stack<TreeNode>();
List<Integer> preorder = new ArrayList<Integer>();
if(root == null){
return preorder;
}
// 根节点进栈
stack.push(root);
while(!stack.empty()){
// 出栈
TreeNode node = stack.pop();
preorder.add(node.val);
if (node.right != null){
//右孩子进栈 注意先是right 因为栈后进先出
stack.push(node.right);
}
if(node.left != null){
//左孩子进栈
stack.push(node.left);
}
}
return preorder;
}
时间复杂度:O(n) 每个结点恰好被遍历一次
空间复杂度:O(n) 递归过程中栈的开销 平均情况下为O(logn),最坏情况下为O(n);
//方法三:非递归 特点:入栈时访问
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if (root == null) {
return res;
}
//这里与上面不同的是 没有new Stack 而是创建LinkedList结构的deque(double-ended queue,双端队列),双端队列是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出
Deque<TreeNode> stack = new LinkedList<TreeNode>();
TreeNode node = root;
//遍历到最左边的节点(同时入栈访问),直至为空,然后取出栈顶节点,把其右孩子压栈。
while (!stack.isEmpty() || node != null) {
while (node != null) {
res.add(node.val);//访问
stack.push(node);//入栈
node = node.left;
}
node = stack.pop();//出栈
node = node.right;
}
return res;
}
时间复杂度:O(n) 每个结点恰好被遍历一次
空间复杂度:O(n) 递归过程中栈的开销 平均情况下为O(logn),最坏情况下为O(n);
// 方法四:分治法 该方法是为了理解分治法
// 递归的定义:把root为根的前序遍历结果返回给List
public ArrayList<Integer> preorderTraversal2(TreeNode root){
ArrayList<Integer> result = new ArrayList<Integer>();
//递归的出口
if(root == null){
return result;
}
//递归的拆解
//Divide 拆分为左子树 右子树
ArrayList<Integer> left = preorderTraversal2(root.left);
ArrayList<Integer> right = preorderTraversal2(root.right);
//Conquer
result.add(root.val);
result.addAll(left);
result.addAll(right);
return result;
}
时间复杂度:O(n) 每个结点恰好被遍历一次
空间复杂度:O(n) 递归过程中栈的开销 平均情况下为O(logn),最坏情况下为O(n);
中序遍历
Leetcode 94题 94. 二叉树的中序遍历 - 力扣(LeetCode) (leetcode-cn.com)
//方法一:递归
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
inorder(root,result);
return result;
}
public void inorder(TreeNode root , List<Integer> result){
//递归的出口:什么时候结束递归 结束条件 不能无限调用
if(root == null){
return;
}
//递归的拆解 一维的完成步骤,根 左子树 右子树
inorder(root.left, result);
result.add(root.val);
inorder(root.right, result);
}
时间复杂度:O(n) 每个结点恰好被遍历一次
空间复杂度:O(n) 递归过程中栈的开销 平均情况下为O(logn),最坏情况下为O(n);
//方法二:非递归 特点:出栈时访问
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Deque<TreeNode> stk = new LinkedList<TreeNode>();
while (root != null || !stk.isEmpty()) {
// 压栈 一直向左找
while (root != null) {
stk.push(root);
root = root.left;
}
//出栈
root = stk.pop();
//访问(放入集合)
res.add(root.val);
//右子树
root = root.right;
}
return res;
}
时间复杂度:O(n),其中 n 为二叉树节点的个数。二叉树的遍历中每个节点会被访问一次且只会被访问一次。
空间复杂度:O(n)。空间复杂度取决于栈深度,而栈深度在二叉树为一条链的情况下会达到 O(n)的级别。
后序遍历
leetcode145 145. 二叉树的后序遍历 - 力扣(LeetCode) (leetcode-cn.com)
//方法一:递归
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
postorder(root,result);
return result;
}
public void postorder(TreeNode root , List<Integer> result){
if(root == null){
return;
}
postorder(root.left, result);
postorder(root.right, result);
result.add(root.val);
}
时间复杂度:O(n) 每个结点恰好被遍历一次
空间复杂度:O(n) 递归过程中栈的开销 平均情况下为O(logn),最坏情况下为O(n);
//方法二:非递归 特点:出栈时访问 入栈的顺序:根 左 右,而栈的后进先出的,所以先让右孩子入栈(后访问),再让左孩子入栈
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if (root == null) {
return res;
}
Deque<TreeNode> stack = new LinkedList<TreeNode>();
TreeNode prev = null;
while (root != null || !stack.isEmpty()) {
// 压栈 一直向左找
while (root != null) {
stack.push(root);
root = root.left;
}
// 回到上一层 找到根节点 才能找到右孩子
root = stack.pop();
//如果右孩子是空,或者被访问过了,则可以访问当前节点
if (root.right == null || root.right == prev) {
res.add(root.val);
prev = root;
root = null;
} else {
//如果不是,把当前节点压栈,进入右子树遍历
stack.push(root);
root = root.right;
}
}
return res;
}