二叉树的前序/后序/中序遍历
//定义二叉树
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;
}
}
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;
}
}
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)
-
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;
}
}