遍历是数据结构进行增删改查的基础,下面对树这种数据结构的遍历进行总结,并给出算法模板。
深度优先遍历
所有遍历都可以拆分成独立的子遍历,并且这种遍历都具有回溯性。所以都可以采用两种方式进行遍历。给出伪代码模板。
递归:
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遍历,将树转为累加和树