算法笔记之二叉树遍历

遍历是数据结构进行增删改查的基础,下面对树这种数据结构的遍历进行总结,并给出算法模板。

深度优先遍历

所有遍历都可以拆分成独立的子遍历,并且这种遍历都具有回溯性。所以都可以采用两种方式进行遍历。给出伪代码模板。
递归:

public void transversal(TreeNode root){
    if(root == null){
        return;
    }
    //..operation..//
    transverseOne();
    //..operation..//
    transverseAnother();
    //..operation..//
}

栈:

public List transversal(TreeNode root){
     List res = new ArrayList<>();
     if(root == null){
        return res;
      }
      //因为使用栈存在重复回溯的问题所以就使用记事本记录
      HashSet accessed = new HashSet<>();
      Stack stack = new Stack<>();
      stack.add(root);
      while (!stack.isEmpty()) {
            TreeNode current = stack.pop();
            if(accessed.contains(current)){
                res.add(current.val);
                continue;
            }
            //添加最后弹出来的节点
            addLastPopNode();
            //添加第二个弹出来的节点
            addSecondPopNode();
            //添加第一个弹出来的节点
            addFirstPopNode();
      }
      return res;
}

前序遍历

前序遍历顺序遵循:根-左-右的顺序

//增加了一个res记录节点值
public void preOrderTransversal(TreeNode root, List res){
    if(root == null){
        return;
    } 
    //记录根节点值操作
    res.add(root.val);
    //transverseOne();
    transverse(root.left,res);
    //transverseAnother();
    transverse(root.right,res);
}

栈:

public List preOrderTransversal(TreeNode root){
     List res = new ArrayList<>();
     if(root == null){
        return res;
      }
      //因为使用栈存在重复回溯的问题所以就使用记事本记录
      //前序遍历,添加accessed和判断逻辑是连接在一起的所以可以省略
      HashSet accessed = new HashSet<>();
      Stack stack = new Stack<>();
      stack.add(root);
      while (!stack.isEmpty()) {
            TreeNode current = stack.pop();
            if(accessed.contains(current)){
                res.add(current.val);
                continue;
            }
            //添加最后弹出来的节点 addLastPopNode();
            if(current.right != null){
                  stack.add(current.right);
            }
            //添加第二个弹出来的节点 addSecondPopNode();
            if(current.left != null){
                  stack.add(current.left);
            }
            //添加第一个弹出来的节点 addFirstPopNode();
            stack.add(current);
            //记录回溯
            accessed.add(current);
      }
      return res;
}

中序遍历

前序遍历顺序遵循:左-根-右的顺序

//增加了一个res记录节点值
public void inOrderTransversal(TreeNode root, List res){
    if(root == null){
        return;
    } 
    //transverseOne();
    transverse(root.left,res);
    //记录根节点值操作
    res.add(root.val);
    //transverseAnother();
    transverse(root.right,res);
}

栈:

public List inOrderTransversal(TreeNode root){
     List res = new ArrayList<>();
     if(root == null){
        return res;
      }
      //因为使用栈存在重复回溯的问题所以就使用记事本记录
      //前序遍历,添加accessed和判断逻辑是连接在一起的所以可以省略
      HashSet accessed = new HashSet<>();
      Stack stack = new Stack<>();
      stack.add(root);
      while (!stack.isEmpty()) {
            TreeNode current = stack.pop();
            if(accessed.contains(current)){
                res.add(current.val);
                continue;
            }
            //添加最后弹出来的节点 addLastPopNode();
            if(current.right != null){
                  stack.add(current.right);
            }
            //添加第二个弹出来的节点 addSecondPopNode();
            stack.add(current);
            //记录回溯
            accessed.add(current);
            //添加第一个弹出来的节点 addFirstPopNode();
            if(current.left != null){
                  stack.add(current.left);
            }
      }
      return res;
}

后序遍历

前序遍历顺序遵循:左-右-根的顺序

//增加了一个res记录节点值
public void postOrderTransversal(TreeNode root, List res){
    if(root == null){
        return;
    } 
    //transverseOne();
    transverse(root.left,res);
    //transverseAnother();
    transverse(root.right,res);
    //记录根节点值操作
    res.add(root.val);
}

栈:

public List postOrderTransversal(TreeNode root){
     List res = new ArrayList<>();
     if(root == null){
        return res;
      }
      //因为使用栈存在重复回溯的问题所以就使用记事本记录
      //前序遍历,添加accessed和判断逻辑是连接在一起的所以可以省略
      HashSet accessed = new HashSet<>();
      Stack stack = new Stack<>();
      stack.add(root);
      while (!stack.isEmpty()) {
            TreeNode current = stack.pop();
            if(accessed.contains(current)){
                res.add(current.val);
                continue;
            }
            //添加最后弹出来的节点 addLastPopNode();
            stack.add(current);
            accessed.add(current);//记录回溯
            //添加第二个弹出来的节点 addSecondPopNode(); 
            if(current.right != null){
                  stack.add(current.right);
            }
            //添加第一个弹出来的节点 addFirstPopNode();
            if(current.left != null){
                  stack.add(current.left);
            }
      }
      return res;
}

使用遍历模板方便记忆、高速套用。举一反三,扩展N叉树以及问题也迎刃而解。

广度优先遍历

层序遍历

层序遍历是一层层的遍历树,符合队列的顺序。
队列:

public void levelOrderTraversal(TreeNode root){
      if(root == null){
        return;
      }
      Queue queue = new LinkedList<>();
      queue.offer(root);
      while(!queue.isEmpty()){
            TreeNode current = queue.poll();
            System.out.println(current.val);
            if(current.left != null){
                  queue.offer(current.left);
            }
            if(current.right != null){
                  queue.offer(current.right);
            }
      }
}

加个盐,将每层的数据汇总

public List> levelOrder(TreeNode root) {
        List> res = new ArrayList<>();
        if (root == null) {
            return res;
        }
        Queue queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            List item = new ArrayList<>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode current = queue.poll();
                item.add(current.val);
                if (current.left != null){
                    queue.offer(current.left);
                }
                if (current.right != null){
                    queue.offer(current.right);
                }
            }
            res.add(item);
        }
        return res;
    }

问题转化的方式,本质不是层序遍历,在遍历到每层的时候,通过递增的层级标记获取对应存储位置,达到了同样的效果。所以严格的讲这是通过深度优先的方式解决了同样的问题,也不失为一种巧妙的思维方式但是不属于层序遍历。

public List> levelOrderTraversal(TreeNode root){
      List> res = new ArrayList<>();
      if(root == null){
            return res;
      }
      levelOrderTraversalHelper(root,res,0);
      return res;
}

public void levelOrderTraversalHelper(TreeNode node,List> res ,int level){
        if(node == null){
              return;
        }
        if(res.size() <= level){
            res.add(new ArrayList<>());
        }
        res.get(level).add(node.val);
        levelOrderTraversalHelper(node.left,res,level+1);
        levelOrderTraversalHelper(node.right,res,level+1);
}

leetcode 题目汇总:

  • 力扣144,二叉树的前序遍历
  • 力扣589,N叉树的前序遍历
  • 力扣94,二叉树的中序遍历
  • 力扣145,二叉树的后序遍历
  • 力扣590,N叉树的后序遍历
  • 力扣429,N叉树的层序遍历
  • 力扣987,二叉树的垂序遍历
  • Morris遍历,将树转为累加和树

你可能感兴趣的:(算法笔记之二叉树遍历)