树的遍历(一)

树的递归遍历框架


二叉树遍历框架,典型的非线性递归遍历结构:

/*
      基本的二叉树结点
*/
class TreeNode{
      int val;
      TreeNode left, right;
}

void traverse(TreeNode root){
      traverse(root.left);
      traverse(root.right);
}

二叉树框架可以扩展为N叉树的遍历框架:

/* 基本的 N 叉树节点 */
class TreeNode {
    int val;
    TreeNode[] children;
}

void traverse(TreeNode root) {
    for (TreeNode child : root.children)
        traverse(child);
}

二叉树的遍历套用上面的框架:

void traverse(TreeNode root) {
    // 前序遍历
    traverse(root.left)
    // 中序遍历
    traverse(root.right)
    // 后序遍历
}

同理,N叉树遍历的递归方法如下:

void traverse(TreeNode root) {
    // 前序遍历
    for (TreeNode child : root.children){
          traverse(child);
    // 后序遍历
}
参考资料:labuladong的算法小抄

树的遍历递归框架如上,下面重点记录非递归遍历树的方法。

二叉树


前序遍历

实现二叉树的非递归前序遍历,我们可以利用这一数据结构,保证栈顶节点就是我们当前的遍历节点。

  1. node = root
  2. 如果 node != null || !stack.isEmpty()
      1)如果node != null
        a. 将值add到res中
        b. 将node推入栈中
        c. node等于node.left,遍历左子树
      2)弹出栈顶,node = node.right开始遍历右子树
  3. 返回结果
public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode node = root;
        while (node != null || !stack.isEmpty()){
            while (node != null ){
                res.add(node.val);
                stack.push(node);
                node = node.left;
            }
            node = stack.pop();
            node = node.right;
        }
        return res;
    }

中序遍历

也是利用栈来实现二叉树的非递归中序遍历。代码基本相同,唯一不同的是node值在入栈后,再在出栈时add到res中。

public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Deque<TreeNode> stack = new LinkedList<>();
        TreeNode node = root;
        while (node != null || !stack.isEmpty()){
            while(node != null){
                stack.push(node);
                node = node.left;
            }    
            node = stack.pop();
            res.add(node.val);   // 出栈add
            node = node.right;
        }
        return res;
    }

后序遍历

同样也是利用栈来完成,该解法的核心是同一个节点进两次栈,第一次出栈是进入该节点的右子树,第二次出栈将值add到res中。

public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        while (true){
            while (root != null){  // 先将root的左子树都压入栈中
                stack.push(root);
                stack.push(root);
                root = root.left;
            }
            if (stack.isEmpty())
                break;
            root = stack.pop();
            if (!stack.isEmpty() && root == stack.peek())
                root = root.right;  // 第一次出栈
            else{
                res.add(root.val);  // 第二次出栈
                root = null;
            }
        }
        return res;
    }

N叉树


前序遍历

实现N叉树的非递归前序遍历,我们同样可以利用这一数据,需要保证栈顶的节点就是我们当前的遍历节点。

  1. 将根节点入栈
  2. 如果栈不为空,弹出栈顶结点
      1)将值add到list中
      2)将所有的子节点逆序推入栈中
  3. 栈为空,返回前序遍历的结果list
public List<Integer> preorder(Node root){
        Stack<Node> stack = new Stack<>();
        ArrayList<Integer> res = new ArrayList<>();
        if (root == null)
            return res;
        stack.push(root);

        while(!stack.isEmpty()){
            Node node = stack.pop();
            res.add(node.val);
            for (int i = node.children.size() - 1; i>=0; i--)
                stack.add(node.children.get(i));     // 孩子节点倒着进栈
        }
        return res;
    }

后序遍历

使用两个栈,第一个栈将节点从上到下,从左到右压入,第二个栈存储后序遍历结果的倒叙,出栈即为后序遍历。

  1. 根节点进stack1
  2. 如果stack1不为空,栈顶节点出栈
      1)压入stack2
      2)孩子节点依次压入stack1
  3. stack2中的节点依次出栈,将值add到res中,返回res
public List<Integer> postorder(Node root) {
        List<Integer> list = new ArrayList<>();
        if (root == null){
            return list;
        }
        Stack<Node> stack1 = new Stack<>();
        Stack<Node> stack2 = new Stack<>();
        stack1.push(root);
        while(!stack1.isEmpty()){
            Node node = stack1.pop();
            stack2.push(node);
            if (node.children != null){
                for (Node child : node.children){
                    stack1.push(child);
                }
            }
        }
        while (!stack2.isEmpty()){
            list.add(stack2.pop().val);
        }
        return list;
    }

层序遍历

层次遍历利用队列,需要保证当前队列是当前层次的节点,因此引入size控制list的大小。

  1. root进入队列
  2. 如果队列不为空
      1)记录当前层次的大小size
      2)依次弹出size个节点,每个节点将值add到list中后,其子节点入队列
      3)将某一层次的节点值list add到res中
  3. 返回结果
public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> lists = new ArrayList<>();
        if (root == null)
            return lists;
        List<TreeNode> nodes = new LinkedList<>();
        nodes.add(root);
        while (!nodes.isEmpty()){
            int size = nodes.size();
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < size; i++){
                TreeNode node = nodes.remove(0);
                list.add(node.val);
                // 二叉树
                if (node.left != null)
                    nodes.add(node.left);
                if (node.right != null)
                    nodes.add(node.right);
                // N叉树
              //for (Node child : node.children)
              //    nodes.add(child);   
            }
            lists.add(list);
        }
        return lists;

总结


树的遍历无非两种方法:递归和非递归。递归框架化较简单,非递归一般都是采用栈来实现。在各种遍历中,后续遍历相对较难,二叉树采取节点重复进栈的方式,N叉树则是利用双栈,从上到下,从左到右进栈后,依次出栈再入另外一个栈,这个栈的出栈的结果即为后序遍历(或者是队列,最后翻转)。

相关题目:

94.二叉树的中序遍历
102.二叉树的层序遍历
144.二叉树的前序遍历
145.二叉树的后序遍历
429.N叉树的层序遍历
589.N叉树的前续遍历
590.N叉树的后续遍历

TO BE CONTINUED...
posted @ 2020-08-20 15:36  Haloya  阅读(263)  评论(0编辑  收藏  举报