二叉树的前序/后序/中序/层序遍历

304 阅读2分钟

二叉树的前序/后序/中序遍历

//定义二叉树
public class TreeNode {
      int val;
      TreeNode left;
      TreeNode right;
      TreeNode() {}
  }

1. 前序遍历

1)迭代法

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();//记录result
        Stack<TreeNode> stack = new Stack<>();//用于记录节点的进出栈情况
        TreeNode node = root; 
        //node != null用来使得一开始能进入 
        //!stack.isEmpty()使得当发现节点为空时,能继续看父节点的右子节点
        //当stack为空时,说明走完了所有节点
        while (!stack.isEmpty() || node != null) {
            if (node != null) {
                stack.push(node);
                res.add(node.val);
                node = node.left;
            } else {//当节点为空时
                //如果当前访问的是左子节点为空,pop出来的是最近的father。
                //如果当前访问的是右子节点为空,pop出来的是最近的father的father,因为father已经在访问他的左子节点兄弟时已经被pop出去了。
                TreeNode father = stack.pop();
                node = father.right;
            }
        }
        return res;
    }
}

B7713D463FBE13600BA90E845237A239.png

2) 递归法

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new LinkedList<Integer>();
        traversal(root, list);
        return list;
    }


    public void traversal(TreeNode node, List<Integer> list) {
        if (node == null) {//终止条件:当节点为空时
            return;
        }
        list.add(node.val);//先记录当前节点
        traversal(node.left, list);//然后再遍历左子树
        traversal(node.right,list);//最后再遍历右子树
    }

2. 中序遍历

1)迭代法

左->根->右

后序遍历与前序遍历的区别在于:前序遍历先记录当前节点,后序遍历在弹栈时记录弹出的节点。

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        TreeNode node = root;
        while (!stack.isEmpty() || node != null) {
            if (node != null) {
                stack.push(node);
                node = node.left;
            } else {
                //如果当前访问的是左子节点为空,pop出来的是最近的father。
                //如果当前访问的是右子节点为空,pop出来的是最近的father的father,因为father已经在访问他的左子节点兄弟时已经被pop出去了。
                TreeNode father = stack.pop();
                res.add(father.val);//左边看完了,获取到根节点,记录下来
                node = father.right;//转到右子节点
            }
        }
        return res;
    }
}

B7713D463FBE13600BA90E845237A239.png

2)递归法

3.后序遍历

1)迭代法1

后序遍历为:左->右->根。可以先得出 根->右->左 形式的遍历,然后反过来就是后序遍历。实现方式上,根->右->左 形式的遍历可以仿照前序遍历根->左->右的结构,将左子节点和右子节点分访问顺序对调一下。从本质上来看,这并不是后序遍历,而依然是前序遍历,因为没有按照后序访问原二叉树各个节点。

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        LinkedList<Integer> res = new LinkedList<>();//注意这个的声明 无泛型
        Stack<TreeNode> stack = new Stack<>();
        TreeNode node = root;
        while (!stack.isEmpty() || node != null) {
            if (node != null) {
                stack.push(node);
                res.addFirst(node.val);//向链表的首部添加数字,这样就实现了“反过来”
                node = node.right;//先访问右子节点
            } else {
                TreeNode tmp = stack.pop();
                node = tmp.left;//再访问左子节点
            }
        }
        return res;
    }
}

2)迭代法2

真正的后序遍历。

基本思想为:按照左右根的顺序依次访问(存储结果集并弹栈)各个节点。

  • 先不断向左下探索,直至遇到null
  • 怎么添加右子节点?
    • 如果右子节点也为null,说明father是叶子节点,直接添加father,图中的情况A
    • 如果右子节点不为null,说明father不是叶子节点,
      • 如果右子节点没有被访问过,需要访问它.图中的情况B(2)

      • 如果右子节点被访问过,说明右子节点已经被添加过了,直接添加father即可。图中的情况B(1)

2161D93AC58488B6B561F8BD6B69214D.png

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        List<Integer> res = new ArrayList<>();
        TreeNode node = root;
        Set<TreeNode> set = new HashSet<>();
        while (!stack.isEmpty() || node != null) {
            if (node != null) {//一直往左下探索
                stack.push(node); 
                node = node.left;
            } else {
                TreeNode father = stack.peek();//不能先pop,只有右子节点访问完了才可以pop
                if (father.right == null) {//【A】探索到null了,如果兄弟节点也是null,那么直接添加father,此时father是叶子节点 
                    res.add(father.val);
                    set.add(father);//靠右的叶子节点,需要记录一下访问过
                    stack.pop();//右子节点为null也算右子节点访问完了,pop
                } else if (set.contains(father.right)) {//【B1】node从father右侧返回null
                    res.add(father.val);
                    set.add(father);
                    stack.pop();//右子节点访问完了,pop
                } else {//【B2】node从father左侧返回null,右子节点没被访问过 
                    node = father.right;
                }
            }
        }
        return res;
    }
}

合并:

class Solution {
    public static List<Integer> postorderTraversal(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();//记录路径信息:用于找到父亲节点
        List<Integer> res = new ArrayList<>();//存储结果
        TreeNode node = root;
        Set<TreeNode> set = new HashSet<>();//记录TreeNode,which左右孩子均已访问完毕
        while(!stack.isEmpty() || node != null) {//stack.isEmpty()为遍历终止条件。因为一开始栈为空,所以需要node !=null来进入循环
            if (node != null) {//当节点不为空时,一直往左下方移动
                stack.push(node);
                node = node.left;
            } else{//当遇到空节后,有两种处理情况 1)从左孩子返回 2)从右孩子返回
                TreeNode father = stack.peek();//当node为空时,1)拿到父亲节点,便于找到同级的右兄弟节点。这里不能pop,因为右孩子还未被访问 2)从右孩子返回
                if (father.right == null || set.contains(father.right)) {//第二次访问到该节点:1)访问完右孩子后返回父亲 2)该父亲节点为叶子节点,孩子为空,返回
                    res.add(father.val);
                    set.add(father);
                    stack.pop();
                } else {//第一次访问到该节点,从左孩子经过父亲,但不访问,目的是找到同级的右节点
                    node = node.right;
                }
            }
        }
        return res;
    }
}

3)递归法

class Solution {
    List<Integer> res = new ArrayList<>();
    public List<Integer> postorderTraversal(TreeNode root) {
        dfs(root);
        return res;
    }

    public void dfs(TreeNode root) {
        if (root == null) {
            return;
        }
        dfs(root.left);
        dfs(root.right);
        res.add(root.val);
    }
}

4.层序遍历

1)第一步:实现广度优先遍历二叉树

基本思路:

class Solution {
    public List<Integer> levelOrder(TreeNode root) {
        List<Integer> res = new ArrayList<>();//结果
        LinkedList<TreeNode> queue = new LinkedList<>();//创建一个队列
        if (root != null) {
            queue.addLast(root);//队列中添加根节点
        }
        while(!queue.isEmpty()) {//当队列非空时
            TreeNode node = queue.removeFirst();//弹出队列中的头部节点
            res.add(node.val);//从队列中弹出后,记录下来
            if (node.left != null) {
                queue.addLast(node.left);//把左孩子添加到队列
            }
            if (node.right != null) {
                queue.addLast(node.right);//把右孩子添加到队列
            }
        }
        return res;
    }
}

2)层序遍历二叉树

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();//结果
        LinkedList<TreeNode> queue = new LinkedList<>();//创建一个队列
        if (root != null) {
            queue.addLast(root);//队列中添加根节点
        }
        while(!queue.isEmpty()) {//当队列非空时
            List<Integer> level = new ArrayList<>();//level用于存储每一层的各个节点
            int size = queue.size();//size为此层上的节点个数
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.removeFirst();//弹出队列中的头部节点,一共弹出size个
                level.add(node.val);//从队列中弹出后,记录下来
                if (node.left != null) {
                    queue.addLast(node.left);//把左孩子添加到队列
                }
                if (node.right != null) {
                    queue.addLast(node.right);//把右孩子添加到队列
                }
            }
            res.add(level);//结果集中加入一层
        }
        return res;
    }
}

ref

www.bilibili.com/video/BV1up… www.bilibili.com/video/BV1Uz…