Loading

LeetCode-Tree-TraversalOfTree 二/N叉树遍历

二叉树遍历

1. 二叉树的前序遍历(144)

前序遍历:先遍历根节点、然后左节点、最后右节点。

1)递归

递归实现前序遍历一

class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    preorderTraversalCore(ans, root);
    return ans;
}

void preorderTraversalCore(List<Integer> ans, TreeNode root){
    if(root==null) return;
    ans.add(root.val);
    preorderTraversalCore(ans, root.left);
    preorderTraversalCore(ans, root.right);
}

}

递归实现前序遍历二

class Solution {
List<Integer> ans = new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {

    if(root==null) return ans;
    ans.add(root.val);
    preorderTraversal(root.left);  //不需要返回值
    preorderTraversal(root.right);
    
    return ans;
}

}

2)迭代

从根节点开始,每次迭代弹出当前栈顶元素,并将其孩子节点压入栈中,先压右孩子再压左孩子。

栈实现迭代法前序遍历-思路一

class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    if(root==null) return ans;
    Stack<TreeNode> stack = new Stack<TreeNode>();
    stack.push(root);
    while(!stack.isEmpty()){
        TreeNode t = stack.pop();
        ans.add(t.val);
        if(t.right != null) stack.push(t.right);
        if(t.left != null) stack.push(t.left);
    }
    return ans;
}

}

时间复杂度:访问每个节点恰好一次,时间复杂度为 O(N) ,其中 N 是节点的个数,也就是树的大小。
空间复杂度:取决于树的结构,最坏情况存储整棵树,因此空间复杂度是 O(N)。


一直遍历左节点至叶子节点,左节点遍历后再遍历右节点

栈实现迭代法前序遍历-思路二

class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
    Deque<TreeNode> stack = new LinkedList<>();
    TreeNode p = root;
    List<Integer> res = new ArrayList<>();
    while (p != null || !stack.isEmpty()) {
        while (p != null) {
            res.add(p.val);
            stack.push(p);
            p = p.left;
        }
        p = stack.pop().right;
    }
    return res;
}

}

3)莫里斯遍历

可以优化空间复杂度。算法不会使用额外空间,只需要保存最终的输出结果。如果实时输出结果,那么空间复杂度是 O(1)。参考

莫里斯前序遍历

class Solution {

public List preorderTraversal(TreeNode root) {
LinkedList output = new LinkedList<>();

TreeNode node = root;
while (node != null) {
  if (node.left == null) {
    output.add(node.val);
    node = node.right;
  }
  else {
    TreeNode predecessor = node.left;
    while ((predecessor.right != null) && (predecessor.right != node)) {
      predecessor = predecessor.right;
    }

    if (predecessor.right == null) {
      output.add(node.val);
      predecessor.right = node;
      node = node.left;
    }
    else{
      predecessor.right = null;
      node = node.right;
    }
  }
}
return output;

}
}

时间复杂度:每个前驱恰好访问两次,因此复杂度是 O(N),其中 N 是顶点的个数,也就是树的大小。
空间复杂度:在计算中不需要额外空间,但是输出需要包含 N 个元素,因此空间复杂度为 O(N)。

2. 二叉树的中序遍历(94)

1)递归

中序遍历:左节点、根、右节点

递归实现中序遍历实现一

class Solution {
List<Integer> ans = new ArrayList<>();  //公共变量,不能放在函数inorderTraversal内
public List<Integer> inorderTraversal(TreeNode root) {     
    if(root == null) return ans;
    inorderTraversal(root.left);
    ans.add(root.val);
    inorderTraversal(root.right);
    return ans;
}

}

递归实现中序遍历实现二

class Solution {
public List <Integer> inorderTraversal(TreeNode root) {
    List <Integer> res = new ArrayList <> ();
    helper(root, res);
    return res;
}

public void helper(TreeNode root, List < Integer > res) {
    if (root == null) return;

    helper(root.left, res);
    res.add(root.val);
    helper(root.right, res);     
}

}

时间复杂度:O(n)。递归函数 T(n) = 2 * T(n/2)+1。
空间复杂度:最坏情况下需要空间 O(n),平均情况为 O(logn)。

2)迭代

该思路同前序迭代遍历思路二是一样的,遍历的过程一样,但是存储节点值的时机不同。

前序:在访问左节点的时候直接保存节点值。

中序:在节点从栈弹出的时候才会保存节点值。

画一棵树,模拟过程。

栈实现迭代法中序遍历-思路一

class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> ans = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();

    TreeNode p = root;
    while(p!=null || !stack.isEmpty()){
        while(p!=null){
            stack.push(p);
            p = p.left;
        }
        TreeNode t = stack.pop();
        ans.add(t.val);
        p = t.right;
    }
    return ans;
}

}

时间复杂度:O(n)。

空间复杂度:O(n)。

3)莫里斯遍历

参考1 参考1

莫里斯中序遍历

class Solution {
public List < Integer > inorderTraversal(TreeNode root) {
    List < Integer > res = new ArrayList < > ();
    TreeNode curr = root;
    TreeNode pre;
    while (curr != null) {
        if (curr.left == null) {
            res.add(curr.val);
            curr = curr.right; // move to next right node
        } else { // has a left subtree
            pre = curr.left;
            while (pre.right != null) { // find rightmost
                pre = pre.right;
            }
            pre.right = curr; // put cur after the pre node
            TreeNode temp = curr; // store cur node
            curr = curr.left; // move cur to the top of the new tree
            temp.left = null; // original cur left be null, avoid infinite loops
        }
    }
    return res;
}

}

3. 二叉树的后序遍历(145)

后序:左节点、右节点、根节点

1)递归

递归实现

class Solution {
List<Integer> ans = new ArrayList<>();
public List<Integer> postorderTraversal(TreeNode root) {
    if(root == null) return ans;

    postorderTraversal(root.left);
    postorderTraversal(root.right);
    ans.add(root.val);
    return ans;
}

}

2)迭代

🍓

迭代实现一 peek(推荐)

class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> ans = new ArrayList<>();

    Stack<TreeNode> stack = new Stack<>();
    TreeNode cur = root;
    TreeNode last = null;

    while(cur!=null || !stack.isEmpty()){
        while(cur != null){
            stack.push(cur);
            cur = cur.left;
        }
        cur = stack.peek();
        if(cur.right == null || cur.right==last){
            ans.add(cur.val);
            stack.pop();
            last = cur;
            cur = null;
        }else{
            cur = cur.right;
        }
    }
    return ans;
}

}

迭代实现二 pop

public List postorderTraversal(TreeNode root) {
    if (root == null) return new ArrayList<Integer>();
    
    TreeNode node = root;
    List<Integer> ret = new ArrayList<Integer>();
    
    Stack<TreeNode> stack = new Stack<TreeNode>();
    while(node != null || !stack.isEmpty()) {
        while (node != null) {
            stack.push(node);
            node = node.left;
        }
        node = stack.pop();
        // 后序遍历
        if (node.right == null) {
            ret.add(node.val);
            node = null;
        }  else if (ret.size() == 0 || !ret.get(ret.size() - 1).equals(node.right.val)) {
            stack.push(node);
            node = node.right;
        } else {
            ret.add(node.val);
            node = null;
        }
    }
    return ret;
}

从根节点开始依次迭代,弹出栈顶元素输出到输出列表中,然后依次压入它的所有孩子节点,按照从上到下、从左至右的顺序依次压入栈中。

因为深度优先搜索后序遍历的顺序是从下到上、从左至右,所以需要将输出列表逆序输出

前序遍历:先压入右节点、在压入左节点。

后序遍历:先压入左节点、在压入右节点,最后对输出的结果逆序。(或一直像头部插入元素、而不是尾部插入元素)

这个题解只是能返回遍历的结果,并不是严格意义上树拓扑结构的遍历。虽然结果是正确,但是如果需要按照后续遍历的顺序对树节点进行访问(或操作),此解法就无法满足。

迭代

class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> ans = new ArrayList();
    if(root==null) return ans;

    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);

    while(!stack.isEmpty()){
        TreeNode t = stack.pop();
        ans.add(0, t.val);  
        if(t.left != null) stack.push(t.left);
        if(t.right != null) stack.push(t.right);
    }
    return ans;
}

}

时间复杂度:访问每个节点恰好一次,因此时间复杂度为 O(N),其中 N 是节点的个数,也就是树的大小。
空间复杂度:取决于树的结构,最坏情况需要保存整棵树,因此空间复杂度为 O(N)。

二叉树前中后序迭代遍历总结

前中后序迭代遍历总结

public List traversal(TreeNode root) {
if (root == null) return new ArrayList<Integer>();

TreeNode node = root;
List<Integer> ret = new ArrayList<Integer>();

Stack<TreeNode> stack = new Stack<TreeNode>();
while(node != null || !stack.isEmpty()) {
    while (node != null) {
        stack.push(node);
        // 先序遍历
        node = node.left;
    }
    node = stack.pop();
    // 中序遍历
    node = node.right;
    // 后序遍历
}
return ret;

}

4. 二叉树的层序遍历(102)

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 
示例: 
二叉树:[3,9,20,null,null,15,7], 
     3
   / \
  9  20
    /  \
   15   7
返回其层次遍历结果: 
 [
  [3],
  [9,20],
  [15,7]
]
 
Related Topics 树 广度优先搜索 

使用队列,首先根元素入队,当队列不为空的时候求当前队列的长度 s_i,依次从队列中取 s_i个元素进行拓展(将左右节点分别加入到队列中),然后进入下一次迭代。

二叉树的层序遍历

class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> ans = new ArrayList<>();
    if(root==null) return ans;
    Queue<TreeNode> queue = new LinkedList<>();
    TreeNode t = root;
    queue.offer(t);
    while(queue.size()!=0){
        int size = queue.size();
        List<Integer> level = new ArrayList<>();
        for(int i=0; i<size; i++){
            t = queue.poll();
            level.add(t.val);
            if(t.left!=null) queue.offer(t.left);
            if(t.right!=null) queue.offer(t.right);
        }
        ans.add(level);
    }
    return ans;
}

}

5. 二叉树的层次遍历 II(107)

给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) 
例如: 
给定二叉树 [3,9,20,null,null,15,7], 
     3
   / \
  9  20
    /  \
   15   7
返回其自底向上的层次遍历为: 
 [
  [15,7],
  [9,20],
  [3]
]

Related Topics 树 广度优先搜索 

1)迭代

同上一提思路一样,结果倒叙输出即可

队列实现-倒叙输出

class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
    List<List<Integer>> ans = new ArrayList<>();
    if(root==null) return ans;
    Queue<TreeNode> queue = new LinkedList<>();
    TreeNode t = root;
    queue.offer(t);
    while(queue.size()!=0){
        int size = queue.size();
        List<Integer> level = new ArrayList<>();
        for(int i=0; i<size; i++){
            t = queue.poll();
            level.add(t.val);
            if(t.left!=null) queue.offer(t.left);
            if(t.right!=null) queue.offer(t.right);
        }
        ans.add(0, level);  //不能使用addAll函数!倒叙插入
    }
    return ans;
}

}

2)递归

🍓 多使用一个形参,记录当前层号。

如果子列表的数量小于层数,说明第一次到达该层,为结果添加一个子列表。注意为了实现自底向上输出,每次都将子列表添加到结果的开头。

子列表的下标对应的是自底向上遍历时结点所在的层号,即 0 对应最后一层。

然后对遍历到的顶点,将其添加到所在层号对应的子列表中即可。

递归实现

class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
    List<List<Integer>> result = new ArrayList<>();
    find(root, 1, result);
    return result;
}
 private void find(TreeNode root, int level, List<List<Integer>> result) {
    if (root == null) {
        return;
    }
    if (level > result.size()) {
        result.add(0, new ArrayList<>());  //向头部插入!
    }
    result.get(result.size() - level).add(root.val);
    find(root.left, level + 1, result);
    find(root.right, level + 1, result);
}

}

6. 二叉树的锯齿形层次遍历(103)

给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。 
例如: 
给定二叉树 [3,9,20,null,null,15,7], 
     3
   / \
  9  20
    /  \
   15   7
返回锯齿形层次遍历如下: 
 [
  [3],
  [20,9],
  [15,7]
]

Related Topics 栈 树 广度优先搜索 

1)队列+奇偶层判断

Java LinkedList add 是加在list尾部。LinkedList push 施加在list头部,等同于addFirst。

队列+奇偶层add/push

class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    List<List<Integer>> ans = new ArrayList<>();
    if(root == null) return ans;

    Queue<TreeNode> queue = new LinkedList<>();
    int level = 0;
    queue.offer(root);

    while(!queue.isEmpty()){
        int size = queue.size();
        LinkedList<Integer> item = new LinkedList();
        for(int i=0;i<size;i++){
            TreeNode t = queue.poll();
            if((level & 1)==0){
                item.add(t.val);
            }else{
                item.push(t.val);
            }

            if(t.left!=null){
                queue.offer(t.left);
            }
            if(t.right!=null){
                queue.offer(t.right);
            }
        }
        ans.add(item);
        level++;
    }
    return ans;
}

}

也可以使用 LinkedList 的 addFirst 与 addLast 方法。

2)递归方法

递归地遍历每个结点的左子结点和右子结点,同时记录每个结点的层号,通过层号的奇偶判断从左到右还是从右到左。

若为从左到右,每次都添加到该层list的尾部。

若为从右到左,每次都添加到该层list的首部。

递归方法

class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    List<List<Integer>> res = new LinkedList<>();
    dfs(root, 1, res);
    return res;
}
private void dfs(TreeNode r, int layer, List<List<Integer>> res) {
    if(r == null)    return;
    if(layer > res.size())//说明第一次到达该层
        res.add(new LinkedList<Integer>());
    if(layer % 2 == 0)
        res.get(layer - 1).add(0, r.val);
    else
        res.get(layer - 1).add(r.val);
    dfs(r.left, layer + 1, res);
    dfs(r.right, layer + 1, res);
}

}

7. 二叉树的垂序遍历(987)

给定二叉树,按垂序遍历返回其结点值。

对位于 (X, Y) 的每个结点而言,其左右子结点分别位于 (X-1, Y-1) 和 (X+1, Y-1)。

把一条垂线从 X = -infinity 移动到 X = +infinity ,每当该垂线与结点接触时,我们按从上到下的顺序报告结点的值( Y 坐标递减)。

如果两个结点位置相同,则首先报告的结点值较小。

按 X 坐标顺序返回非空报告的列表。每个报告都有一个结点值列表。

提示:
树的结点数介于 1 和 1000 之间。
每个结点值介于 0 和 1000 之间。
输入:[3,9,20,null,null,15,7]
输出:[[9],[3,15],[20],[7]]
解释: 
在不丧失其普遍性的情况下,我们可以假设根结点位于 (0, 0):
然后,值为 9 的结点出现在 (-1, -1);
值为 3 和 15 的两个结点分别出现在 (0, 0) 和 (0, -2);
值为 20 的结点出现在 (1, -1);
值为 7 的结点出现在 (2, -2)。
输入:[1,2,3,4,5,6,7]
输出:[[4],[2],[1,5,6],[3],[7]]
解释:
根据给定的方案,值为 5 和 6 的两个结点出现在同一位置。
然而,在报告 "[1,5,6]" 中,结点值 5 排在前面,因为 5 小于 6。

记录坐标

1)使用深度优先搜索找到每个节点的坐标(创建一个结构,同时存储节点值与节点坐标)。保持当前节点 (x, y) 移动的过程中,坐标变化为 (x-1, y+1) 或 (x+1, y+1) 取决于是左孩子还是右孩子。

2)通过 x 坐标排序,再根据 y 坐标排序,这样确保以正确的顺序添加到答案中。

记录坐标

class Solution {
List<Location> locations;
public List<List<Integer>> verticalTraversal(TreeNode root) {
    locations = new ArrayList<>();
    dfs(root, 0, 0);

    Collections.sort(locations);  //!!!

    List<List<Integer>> ans = new ArrayList<>();
    ans.add(new ArrayList<Integer>());

    int pre = locations.get(0).x;

    for(Location t : locations){
        if(pre != t.x){
            pre = t.x;
            ans.add(new ArrayList<Integer>());
        }
        ans.get(ans.size() - 1).add(t.val);
    }

    return ans;
}

public void dfs(TreeNode root, int x, int y){
    if(root!=null){
        locations.add(new Location(x, y, root.val));
        dfs(root.left, x-1, y+1);  //!!!
        dfs(root.right, x+1, y+1);
    }
}

}

class Location implements Comparable{

int x,y,val;
Location(int x, int y, int val){
    this.x = x;
    this.y = y;
    this.val = val;
}

@Override
public int compareTo(Location that){
    if(this.x != that.x){
        return Integer.compare(this.x, that.x);
    }else if(this.y != that.y){
        return Integer.compare(this.y, that.y);
    }else{
        return Integer.compare(this.val, that.val);
    }
}

}

注:存储坐标时,子节点对y+1,与题目y-1(从上到下,y递减)不同,是因为程序中判断y是递增的。

N叉树

N叉树的层序遍历(429)

给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。

例如,给定一个 3叉树 :
返回其层序遍历: 
[
    [1],
    [3,2,4],
    [5,6]
]

说明: 
树的深度不会超过 1000。 
树的节点总数不会超过 5000。
 
Related Topics 树 广度优先搜索 
N 叉树的层序遍历-队列

class Solution {
public List<List<Integer>> levelOrder(Node root) {
    List<List<Integer>> ans =  new ArrayList<>();
    if(root == null) return ans;

    // Queue<Node> queue = new Queue<>();
    Queue<Node> queue = new LinkedList<>();
    queue.offer(root);

    while(!queue.isEmpty()){
        int size = queue.size();
        List<Integer> level = new ArrayList<>();
        for(int i=0; i<size; i++){
            Node t = queue.poll();
            level.add(t.val);
            /*if(t.children != null){
                for(Node per: t.children){
                    queue.offer(per);
                }
            }*/
            queue.addAll(t.children);
        }
        ans.add(level);
    }
    return ans;
}

}

N 叉树的层序遍历-递归

class Solution {
private List<List<Integer>> result = new ArrayList<>();

public List<List<Integer>> levelOrder(Node root) {
    if (root != null) traverseNode(root, 0);
    return result;
}

private void traverseNode(Node node, int level) {
    if (result.size() <= level) {
        result.add(new ArrayList<>());
    }
    result.get(level).add(node.val);
    for (Node child : node.children) {
        traverseNode(child, level + 1);
    }
}

}

N叉树的前序遍历(589)

给定一个 N 叉树,返回其节点值的前序遍历。

例如,给定一个 3叉树 :
返回其前序遍历: [1,3,5,6,2,4]。 
说明: 递归法很简单,你可以使用迭代法完成此题吗? Related Topics 树 
N 叉树的前序遍历

代码

N 叉树的前序遍历

代码

N叉树的后序遍历(590)

展示代码

代码

posted @ 2020-08-10 14:53  喵喵巫  阅读(240)  评论(0编辑  收藏  举报