二元樹的按層遍歷

2022-06-08 21:01:19

作者:Grey

原文地址:二元樹的按層遍歷

說明

本文主要介紹了二元樹的按層遍歷。並且分別用如下三種方式實現:

  1. 雜湊表結合LinkedList
  2. 使用系統自帶的LinkedList
  3. 自定義佇列

以上方法只是空間複雜度有所差異,時間複雜度上都是一樣的。

範例二元樹

這個二元樹按層次遍歷的結果就是

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

資料結構

public static 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 static List<List<Integer>> levelOrder(TreeNode head) {
        if (head == null) {
            return new ArrayList<>();
        }
        List<List<Integer>> ans = new ArrayList<>();
        // 記錄某個節點在第幾層
        Map<TreeNode, Integer> map = new HashMap<>();
        Queue<TreeNode> queue = new LinkedList<>();
        // 當前是第幾層
        int curLevel = 0;
        TreeNode cur = head;
        queue.offer(cur);
        map.put(cur, curLevel);
        List<Integer> levelRecords = new ArrayList<>();
        while (!queue.isEmpty()) {
            TreeNode c = queue.poll();
            int level = map.get(c);
            if (c.left != null) {
                queue.offer(c.left);
                map.put(c.left, level + 1);
            }
            if (c.right != null) {
                queue.offer(c.right);
                map.put(c.right, level + 1);
            }
            if (curLevel == level) {
                levelRecords.add(c.val);
            } else {
                ans.add(levelRecords);
                levelRecords = new ArrayList<>();
                levelRecords.add(c.val);
                curLevel = level;
            }
        }
        // 記得要存最後一層的資料
        ans.add(levelRecords);
        return ans;
    }

方法2省略了一個雜湊表,使用了兩個變數來判斷層數的變化,分別是:

// 遍歷到的當前層的最後一個位置
TreeNode curEnd; 
// 下一層的最後一個位置
TreeNode nextEnd;

在佇列每次彈出元素的時候,設定nextEnd變數,同時,如果彈出的元素等於curEnd,說明已經到當前層的結尾了,就可以收集這一層的答案了。

方法2的完整程式碼如下

public static List<List<Integer>> levelOrder2(TreeNode head) {
        if (head == null) {
            return new ArrayList<>();
        }
        List<List<Integer>> ans = new ArrayList<>();
        List<Integer> levelRecords = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        TreeNode curEnd = head;
        TreeNode nextEnd = null;
        queue.offer(curEnd);
        while (!queue.isEmpty()) {
            TreeNode c = queue.poll();
            levelRecords.add(c.val);
            if (c.left != null) {
                queue.offer(c.left);
                // 彈出的時候,設定nextEnd
                nextEnd = c.left;
            }
            if (c.right != null) {
                queue.offer(c.right);
               // 彈出的時候,設定nextEnd
                nextEnd = c.right;
            }
            if (c == curEnd) {
                // 即將要來到新的一層了
                curEnd = nextEnd;
                ans.add(levelRecords);
                levelRecords = new ArrayList<>();
            }
        }
        return ans;
    }

方法3只是把方法2中的連結串列和佇列換成自己實現的連結串列和佇列結構,大思路上和方法2一樣,我們可以自己實現一個連結串列和佇列,實現最簡單的polloffer方法即可,自定義的連結串列如下:

  // 自定義連結串列
  public static class MyNode {
        public TreeNode data;
        public MyNode next;
	
        public MyNode(TreeNode node) {
            data = node;
        }
    }
	// 自定義佇列
    public static 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 static 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> item = 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;
            }
            item.add(c.data.val);
            if (curEnd.data == c.data) {
                ans.add(item);
                item = new ArrayList<>();
                curEnd = nextEnd;
            }
        }
        return ans;
    }

二元樹的先,中,後序遍歷

見筆記:二元樹的先,中,後序遍歷

更多

演演算法和資料結構筆記

參考資料

演演算法和資料結構體系班-左程雲