二叉树遍历方法
Preorder 前序遍历——根左(子树)右(子树)
Inorder中序遍历——左(子树)根右(子树)
Postorder后序遍历——左(子树)右(子树)根
递归三要素:
1、递归的定义: 递归这个函数干了一件什么事情,一句话描述清楚,确定返回值、传入参数、调用
2、递归的出口: 什么时候是结束递归,退出条件是什么
3、递归的拆解: 将问题拆分为更小的问题,将数据规模变小,拆分成更小的子问题
遍历与分治法的区别是:
1、返回值不同 遍历返回值通常是void 分治法可有返回值
2、策略不同 遍历相当于整体执行,分治法相当于分成多个部分再合成一个整体,分而治之。
思路: 碰到二叉树的问题,就想想整棵树在该问题上的结果和左右子树在该问题上的结果之间 的联系是什么
前序遍历
leetcode 144题 144. 二叉树的前序遍历 - 力扣(LeetCode) (leetcode-cn.com)
//方法一:递归
class Solution {
public List preorderTraversal(TreeNode root) {
List result = new ArrayList();
preorder(root,result);
return result;
}
// 递归的定义:将root为根的preorder加入到result里面
public void preorder(TreeNode root , List 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 preorderTraversal(TreeNode root){
// 创建栈
Stack stack = new Stack();
List preorder = new ArrayList();
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 preorderTraversal(TreeNode root) {
List res = new ArrayList();
if (root == null) {
return res;
}
//这里与上面不同的是 没有new Stack 而是创建linkedList结构的deque(double-ended queue,双端队列),双端队列是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出
Deque stack = new linkedList();
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 preorderTraversal2(TreeNode root){
ArrayList result = new ArrayList();
//递归的出口
if(root == null){
return result;
}
//递归的拆解
//Divide 拆分为左子树 右子树
ArrayList left = preorderTraversal2(root.left);
ArrayList 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 ListinorderTraversal(TreeNode root) { List result = new ArrayList (); inorder(root,result); return result; } public void inorder(TreeNode root , List 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 inorderTraversal(TreeNode root) { List res = new ArrayList (); Deque stk = new linkedList (); 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 ListpostorderTraversal(TreeNode root) { List result = new ArrayList (); postorder(root,result); return result; } public void postorder(TreeNode root , List 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 postorderTraversal(TreeNode root) { List res = new ArrayList (); if (root == null) { return res; } Deque stack = new linkedList (); 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; }



