这篇文章的目的只是整理几个常用的二叉树递归/非递归遍历的模板函数,方便用到的时候凭借记忆写出来即可。内容没多少技术含量,写给自己看的,权当参考。
前序遍历
递归:很简单,按照根左右的逻辑访问就是。
public List<Integer> preorderTraversal(TreeNode root){
List<Integer> res = new ArrayList<Integer>();
inorder(root, res);
return res;
}
public void preorder(TreeNode root, List<Integer> res){
if(root == null) return;
res.add(root.val);
preorder(root.left, res);
preorder(root.right, res);
}
在所有树状结构的数据结构当中,递归真的是一种最最省事的写法了。如果题目没有要求,建议直接递归就vans了。
非递归(这才是本篇文章的重点):用一个栈,前序遍历的非递归是三种非递归当中最好理解的一种了,先把根节点入栈,再遵循当前节点弹栈->当前节点左节点入栈->当前节点右节点入栈即可。
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
if(root != null){
stack.push(root);
}
TreeNode temp;
while(!stack.isEmpty()){
temp = stack.pop();
res.add(temp.val);
if(temp.right != null){
stack.push(temp.right);
}
if(temp.left != null){
stack.push(temp.left);
}
}
return res;
}
中序遍历
递归:还是很简单,左根右结构的访问即可。
public List<Integer> inorderTraversal(TreeNode root){
List<Integer> res = new ArrayList<Integer>();
inorder(root, res);
return res;
}
public void inorder(TreeNode root, List<Integer> res){
if(root == null) return;
inorder(root.left, res);
res.add(root.val);
inorder(root.right, res);
}
非递归:使用一个栈,因为是左根右结构的遍历,所以看到当前节点有左节点就先把它加入栈,直到没有左节点了,再弹栈当前节点,如果当前节点有右子树的话就切换到右子树上即可。
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode temp = root; //temp变量只在入栈预处理阶段使用
TreeNode cur;
while(temp != null || !stack.isEmpty()){
// 只要有左节点,就压栈
if(temp != null){
stack.push(temp);
temp = temp.left;
}else{
// 没有左节点了,访问当前节点
cur = stack.pop();
res.add(cur.val);
// 有右子树的话就切换到右子树上
if(cur.right != null){
temp = cur.right;
}
}
}
return res;
}
后序遍历
递归:……(省略)
public List<Integer> inorderTraversal(TreeNode root){
List<Integer> res = new ArrayList<Integer>();
inorder(root, res);
return res;
}
public void inorder(TreeNode root, List<Integer> res){
if(root == null) return;
inorder(root.left, res);
inorder(root.right, res);
res.add(root.val);
}
非递归:也是使用一个栈,这里和中序遍历的非递归不同之处在于,要用一个指针last记录下上一个节点,如果是从last过来的,就直接遍历当前节点结束(因为此时已经走完了一轮左->右->根的流程),如果不是就切换到右子树上去。
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode temp = root; //temp变量只在入栈预处理阶段使用
TreeNode cur;
TreeNode last = null; // last和cur只在出栈遍历阶段使用
while(temp != null || !stack.isEmpty()){
// 只要有左节点,就压栈
if(temp != null){
stack.push(temp);
temp = temp.left;
}else{
// 确定是否切换到右子树
// 如果有右节点,又不是刚刚从右子树过来的,就切换到右子树
cur = stack.peek();
if(cur.right != null && cur.right != last){
temp = cur.right;
}else{
// 左右都遍历完了,遍历当前节点并弹栈
// 记录last节点
res.add(cur.val);
stack.pop();
last = cur;
}
}
}
return res;
}
层序遍历
这个一般人一开始没法想到递归了。但是也很简单,用一个队列保存节点即可。这个顺序很好理解,遍历到一个节点的时候,就把它的左节点->右节点加入队列,只要队列不空,就一直访问下去。
还有一个问题是如何记录层的信息,即如何把每一层的节点分开的问题。只要在每一层开始的时候,用queue.size()记录下队列中当前的节点数,这就是当前这一层的所有节点了。然后用一个变量保存遍历过的节点数,只要这个变量与size()相等了,说明当前这一层遍历完了,清空变量再用size()得到下一层的节点数,如此往复即可。
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> levels = new ArrayList<List<Integer>>();
if (root == null) return levels;
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
int level = 0;
while ( !queue.isEmpty() ) {
levels.add(new ArrayList<Integer>());
int level_length = queue.size(); //记录当前层的长度
// 在当前层里记录每一个节点,同时把下一层的节点加入队列
for(int i = 0; i < level_length; ++i) {
TreeNode node = queue.poll();
levels.get(level).add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
level++;
}
return levels;
}
但是层序遍历其实也可以通过递归来求解的,下面贴出递归的代码:
List<List<Integer>> levels = new ArrayList<List<Integer>>();
public void helper(TreeNode node, int level) {
// start the current level
if (levels.size() == level)
levels.add(new ArrayList<Integer>());
// fulfil the current level
levels.get(level).add(node.val);
// process child nodes for the next level
if (node.left != null)
helper(node.left, level + 1);
if (node.right != null)
helper(node.right, level + 1);
}
public List<List<Integer>> levelOrder(TreeNode root) {
if (root == null) return levels;
helper(root, 0);
return levels;
}
以上层序遍历的两种解法均来自leetcode官方题解,递归解法用得少,只需要掌握非递归解法即可。
其实上面说到的非递归解法中,前中后序遍历的三种解法可以视作是dfs,而最后一种层序遍历则可视作是bfs。这也启发我们想到,dfs相关的思想(甚至是回溯相关的思想)可以用栈来解决,而bfs可以用队列来解决。在不用递归的情况下,做出递归的效果,一般依靠的都是这两种数据结构。