一、前言
学习二叉树也有一段时间了,但一直没有系统的整理一些最基础的算法问题,这篇文章就当是个总结吧。二叉树的数据结构相信大家已经烂熟于心了,在此不多说了,直接上代码。
二、先从递归说起
二叉树最明显的特征就是递归结构,很多关于二叉树的算法题都与递归扯不开关系。
1.递归前中后序遍历二叉树:
private List<Integer> inorderTraversal(TreeNode root) {
//我们用一list来存储结果
List<Integer> res = new ArrayList<>();
visit(root, res);
return res;
}
private void visit(TreeNode root, List<Integer> res) {
if (root != null) {
//对结果集进行添加
res.add(root.val);
//递归左子树
if (root.left != null) {
visit(root.left, res);
}
//递归右子树
if (root.right != null) {
visit(root.right, res);
}
}
}
而对于中序和后序遍历,只是对结果集进行添加这一步的位置不同。中序是一种左中右的顺序,而后续则是左右中的顺序。
2.递归层次遍历二叉树
层次遍历二叉树相对前面来说有些麻烦,我们需要之定一个变量来记录当前层次:
private List<List<Integer>> levels = new ArrayList<>(); //结果集
private List<List<Integer>> levelOrder(TreeNode root) {
if (root == null) {
return levels;
}
//默认从第0层开始遍历
helper(root, 0);
return levels;
}
private void helper(TreeNode root, int level) {
//如果当前层的元素已经全部遍历完了,就添加进结果集
if (levels.size() == level) {
levels.add(new ArrayList<>(level));
}
//从根结点开始添加
levels.get(level).add(root.val);
//递归左子树并给层+1,因为孩子结点是位于下一层的
if (root.left != null) {
helper(root.left, level + 1);
}
//同理
if (root.right != null) {
helper(root.right, level + 1);
}
}
三、进阶到非递归遍历
非递归遍历是个难点,同样也是重点,需要反复的去理解。
1.非递归前序遍历二叉树:
private List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
LinkedList<TreeNode> stack = new LinkedList<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
while (curr != null) {
//一开始将根结点加入结果集
res.add(curr.val);
//进行入栈操作并遍历左子树
stack.push(curr);
curr = curr.left;
}
//出栈并遍历右子树
curr = stack.pop();
curr = curr.right;
}
return res;
}
2.非递归中序遍历二叉树:
private List<Integer> inorderTraversal2(TreeNode root) {
//结果集
List<Integer> res = new ArrayList<>();
//非递归遍历的核心就是利用栈这一数据结构
LinkedList<TreeNode> stack = new LinkedList<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
//从根结点开始,每次遇到有左孩子的情况就进行压栈操作
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
//弹出二叉树中左下角的元素
curr = stack.pop();
//将其添加到结果集中去
res.add(curr.val);
//一开始curr.right是null,所以执行完第一次后继续进行弹栈操作。
//第二次的时候这里就是是最左边的元素的父结点的右孩子了
curr = curr.right;
}
return res;
}
3.非递归后续遍历二叉树:
private List<Integer> postorderTraversal(TreeNode root) {
LinkedList<TreeNode> stack = new LinkedList<>();
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
//逆向思维,每次都把后面的元素加入到第一位
//先序遍历是根左右,后续遍历是左右根
//把先序遍历的根左右变成根右左,然后在逆过来就行
res.add(0, node.val);
if (node.left != null) {
stack.push(node.left);
}
if (node.right != null) {
stack.push(node.right);
}
}
return res;
}
4.非递归层次遍历:
利用队列这一数据结构来存储信息
private List<List<Integer>> levelOrder2(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) {
return result;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
//保存每一层的信息
List<Integer> level = new ArrayList<>();
int level_length = queue.size();
for (int i = 0; i < level_length; i++) {
TreeNode node = queue.remove();
level.add(node.val);
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
//将每一层的信息添加到结果集中
result.add(level);
}
return result;
}