Loading

二叉树的按层遍历相关问题

二叉树的按层遍历相关问题

作者:Grey

原文地址:

博客园:二叉树的按层遍历相关问题

CSDN:二叉树的按层遍历相关问题

说明

本文主要介绍了二叉树的按层遍历。并且分别用如下三种方式实现:

  1. 哈希表结合 LinkedList

  2. 使用系统自带的 LinkedList

  3. 自定义队列

以上方法只是空间复杂度有所差异,时间复杂度上都是一样的。

示例二叉树

img

这个二叉树按层次遍历的结果就是

1->2->3->4->5->6->7->8->9->10->11->12->13

数据结构

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;
    }
}

测评链接

LeetCode 102. Binary Tree Level Order Traversal

流程

整个过程最核心的地方就是需要记录当前层什么时候遍历完毕以及当前弹出的节点在第几层

方法1中,使用哈希表来存每个节点当前所在的层,头节点默认在第 0 层,且将头节点首先入队列,然后在弹出的过程中,将弹出节点的子节点放入哈希表,且把层数设置为当前节点的层数 +1,同时把子节点放入队列,然后进行同样的队列弹出操作,直到队列空。注:在队列的弹出过程中,要判断弹出的节点是否是当前层,如果是当前层,表示当前层没有遍历结束,如果不是当前层,则一定是下一层(因为每次加入哈希表的节点均会对层数加一),此时则说明当前层遍历结束,开始下一层的遍历了。

方法1的完整代码如下

public List<List<Integer>> levelOrder(TreeNode root) {
    if (null == root) {
        return new ArrayList<>();
    }
    // 存每个节点在哪一层
    Map<TreeNode, Integer> map = new HashMap<>();
    // 用于遍历树的节点
    Queue<TreeNode> queue = new LinkedList<>();
    List<List<Integer>> ans = new ArrayList<>();
    map.put(root, 0); // 根节点在第0层
    queue.offer(root);
    int curLevel = 0;
    List<Integer> everyLevel = new ArrayList<>();
    while (!queue.isEmpty()) {
        TreeNode c = queue.poll();
        // 弹出节点在第几层
        int nodeLevel = map.get(c);
        if (c.left != null) {
            queue.offer(c.left);
            map.put(c.left, nodeLevel + 1);
        }
        if (c.right != null) {
            queue.offer(c.right);
            map.put(c.right, nodeLevel + 1);
        }
        if (curLevel == nodeLevel) {
            // 弹出节点就是当前节点所在的层,说明没有收集完毕,继续收集
            everyLevel.add(c.val);
        } else {
            // 弹出节点不是当前节点所在层,则一定是下一层的节点(因为每次map进入的节点层数只加一)所在层
            // 此时说明当前层已经遍历结束
            ans.add(everyLevel);// 可以收集当前层的完整信息
            everyLevel = new ArrayList<>(); // 继续为下一层开辟空间
            everyLevel.add(c.val);
            curLevel = nodeLevel;
        }
    }
    ans.add(everyLevel);
    return ans;
}

方法2省略了一个哈希表,使用了两个变量来判断层数的变化,分别是:

// 遍历到的当前层的最后一个位置
TreeNode curEnd; 
// 下一层的最后一个位置
TreeNode nextEnd;

在队列每次弹出元素的时候,设置 nextEnd 变量,同时,如果弹出的元素等于 curEnd,说明已经到当前层的结尾了,就可以收集这一层的答案了。

方法2的完整代码如下

public List<List<Integer>> levelOrder(TreeNode root) {
    if (null == root) {
        return new ArrayList<>();
    }
    // 用于遍历树的节点
    Queue<TreeNode> queue = new LinkedList<>();
    List<List<Integer>> ans = new ArrayList<>();
    queue.offer(root);
    List<Integer> everyLevel = new ArrayList<>();
    TreeNode nextEnd = null;
    TreeNode curEnd = root;
    while (!queue.isEmpty()) {
        TreeNode c = queue.poll();
        if (c.left != null) {
            nextEnd = c.left;
            queue.offer(c.left);
        }
        if (c.right != null) {
            nextEnd = c.right;
            queue.offer(c.right);
        }
        if (curEnd == c) {
            // 当前层结束了
            everyLevel.add(c.val);
            ans.add(everyLevel);
            everyLevel = new ArrayList<>();
            curEnd = nextEnd;
        } else {
            // 当前层还没结束
            everyLevel.add(c.val);
        }
    }
    return ans;
}

方法3只是把方法2中的链表和队列换成自己实现的链表和队列结构,大思路上和方法2一样,我们可以自己实现一个链表和队列,实现最简单的polloffer方法即可,代码可参考用单链表实现栈和队列
自定义的链表如下:


// 自定义节点
public class MyNode {

    public TreeNode data;
    public MyNode next;

    public MyNode(TreeNode node) {
        data = node;
    }
}
// 自定义队列
public class MyQueue {

    public MyNode front;
    public MyNode end;
    public int size;

    public MyQueue() {
        front = null;
        end = null;
    }

    public void offer(MyNode c) {
        size++;
        if (front == null) {
            front = c;
        } else {
            end.next = c;
        }
        end = c;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public MyNode poll() {
        size--;
        MyNode ans = front;
        front = front.next;
        ans.next = null;
        return ans;
    }
}

然后把方法2中的 Java 自带的LinkedList换成我们自己实现的链表和队列,完整代码如下

public List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> ans = new ArrayList<>();
    if (root == null) {
        return ans;
    }
    MyNode head = new MyNode(root);
    MyQueue queue = new MyQueue();
    queue.offer(head);
    MyNode curEnd = head;
    MyNode nextEnd = null;
    List<Integer> everyLevel = new ArrayList<>();
    MyNode t;
    while (!queue.isEmpty()) {
        MyNode c = queue.poll();
        if (c.data.left != null) {
            t = new MyNode(c.data.left);
            queue.offer(t);
            nextEnd = t;
        }
        if (c.data.right != null) {
            t = new MyNode(c.data.right);
            queue.offer(t);
            nextEnd = t;
        }
        everyLevel.add(c.data.val);
        if (curEnd.data == c.data) {
            ans.add(everyLevel);
            everyLevel = new ArrayList<>();
            curEnd = nextEnd;
        }
    }
    return ans;
}

类似问题一:二叉树自底向上层序遍历

给你二叉树的根节点 root ,返回其节点值自底向上的层序遍历 。(即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

题目链接见:LeetCode 107. Binary Tree Level Order Traversal II

主要思路,由于要自底向上,所以,可以将按层收集的数据结构由队列改成栈就可以了,然后到最后再把栈内收集的结果弹出即可。

代码如下

public List<List<Integer>> levelOrderBottom(TreeNode root) {
    if (null == root) {
        return new ArrayList<>();
    }
    Deque<List<Integer>> stack = new ArrayDeque<>();
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> levelRecords = new ArrayList<>();
    TreeNode curEnd = root;
    TreeNode nextEnd = null;
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        TreeNode poll = queue.poll();
        levelRecords.add(poll.val);
        if (null != poll.left) {
            queue.offer(poll.left);
            nextEnd = poll.left;
        }
        if (null != poll.right) {
            queue.offer(poll.right);
            nextEnd = poll.right;
        }
        if (poll == curEnd) {
            curEnd = nextEnd;
            stack.push(levelRecords);
            levelRecords = new ArrayList<>();
        }
    }
    while (!stack.isEmpty()) {
        result.add(stack.poll());
    }
    return result;
}

本题还有另外一个思路,即一次搞定一层,然后把每一层的数据都插入到一个链表的头部(模拟栈)

完整代码如下:

public List<List<Integer>> levelOrderBottom(TreeNode root) {
    List<List<Integer>> ans = new LinkedList<>();
    if (root == null) {
        return ans;
    }
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);
    while (!queue.isEmpty()) {
        int size = queue.size();
        List<Integer> curAns = new LinkedList<>();
        for (int i = 0; i < size; i++) {
          // 一次搞定一层
            TreeNode curNode = queue.poll();
            curAns.add(curNode.val);
            if (curNode.left != null) {
                queue.add(curNode.left);
            }
            if (curNode.right != null) {
                queue.add(curNode.right);
            }
        }
        // 每次把最新的那一层插入到链表的头部,模拟栈
        ans.addFirst(curAns);
    }
    return ans;
}

类似问题二:以数组的形式返回每一层节点的平均值

给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。

题目链接见:LeetCode 637. Average of Levels in Binary Tree

本题的思路也类似,就是用一个变量先记录每一层的数量,然后用另外一个变量记录每一层的结点值之和,到遍历到下一层的时候,结算上一层结点值的平均值即可。

代码如下

public List<Double> averageOfLevels(TreeNode root) {
    if (null == root) {
        return new ArrayList<>();
    }
    List<Double> result = new ArrayList<>();
    Queue<TreeNode> queue = new LinkedList<>();
    TreeNode curEnd = root;
    TreeNode nextEnd = null;
    int numOfNodes = 0; // 记录每一层的节点个数
    double sumOfNodesVal = 0d; // 记录每一层的节点平均值(累计值除以个数)
    queue.offer(root);
    while (!queue.isEmpty()) {
        TreeNode cur = queue.poll();
        if (null != cur.left) {
            queue.offer(cur.left);
            nextEnd = cur.left;
        }
        if (null != cur.right) {
            queue.offer(cur.right);
            nextEnd = cur.right;
        }
        numOfNodes++;
        sumOfNodesVal += cur.val;
        if (cur == curEnd) {
            result.add(sumOfNodesVal / numOfNodes);
            sumOfNodesVal = 0d;
            numOfNodes = 0;
            curEnd = nextEnd;
        }
    }
    return result;
}

类似问题三:填充每个节点的下一个右侧节点指针

填充每个节点的下一个右侧节点指针

题目链接见:

LeetCode 116. Populating Next Right Pointers in Each Node

LeetCode 117. Populating Next Right Pointers in Each Node II

本题的主要思路也是基于二叉树的按层遍历,只不过在遍历完每一层后,把这一层的数据用链表串起来。

所以,需要获取每一层的数量,且设置一个 pre 变量,用于记录每次遍历完一层的最后一个位置,因为要用这个位置去连下一层的开始位置。

完整代码如下

public Node connect(Node root) {
    if (null == root) {
        return null;
    }
    Node pre = null;
    int size;
    Queue<Node> queue = new LinkedList<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        size = queue.size();
        // 把每一层用链表串好,pre记录每一层的最后那个位置
        // 用于和下一层的开头连接
        // 每次把一层搞定
        for (; size > 0; size--) {
            Node p = queue.poll();
            if (pre != null) {
                pre.next = p;
            }
            pre = p;
            if (null != p.left) {
                queue.offer(p.left);
            }
            if (null != p.right) {
                queue.offer(p.right);
            }
        }
        pre = null;
    }
    return root;
}

更多

算法和数据结构学习笔记

算法和数据结构学习代码

posted @ 2022-06-08 19:05  Grey Zeng  阅读(786)  评论(0编辑  收藏  举报