这是我参与8月更文挑战的第28天,活动详情查看:8月更文挑战
今天在复习树相关知识的时候想到了如上的问题,如果用递归来解决的话实际上是很简单的。
但是:如果使用迭代来替换递归,一时半会就想不出来了(查了一下,其实如果换成迭代,这个题目的难度就从easy到medium甚至hard了,因此一时间想不出来别灰心),因此现在总结一下树的递归转换到迭代的过程。
我们对递归下一个边界条件:递归:调用自身直到遇到边界条件。
在递归的时候,其实隐含的流程是:
- 将当前的数据,压入方法栈
- 通过传入的数据,进行下一步递归
因此,我们在递归转换到迭代的过程中,最重要的其实是确定当前位置。
拿上面的遍历举例:
- 前序遍历是最简单的,所以我们从这里开始。
//递归
public static void preOrderTraversalR(List<Integer> list,TreeNode cur){
list.add(cur.val);
inorderTraversalR(list,cur.left);
inorderTraversalR(list,cur.right);
}
//迭代
public static List<Integer> preOrderTraversal(TreeNode root) {
Deque<TreeNode> stk = new ArrayDeque<>();
List<Integer> res = new ArrayList<>();
//仔细对比一下这里和循环的对应关系
while(root!=null || !stk.isEmpty()){
if(null == root) root = stk.pop();
res.add(root.val);
if(null!=root.right)stk.push(root.right);
root = root.left;
}
return res;
}
经过比较可以发现:
-
迭代的时候,和递归相比我们的操作有一些固定的关系:
- 先进行递归的,在迭代中是下一次执行对象
- 我们本次迭代的整体流程是直到找到下一个加入结果集的对象结束
那么,我们根据这个发现,试着将中序遍历写出来:
public static void inorderTraversalR(List<Integer> list,TreeNode cur){
inorderTraversalR(list,cur.left);
list.add(cur.val);
inorderTraversalR(list,cur.right);
}
public static List<Integer> inorderTraversal(TreeNode root) {
Deque<TreeNode> stk = new LinkedList<>();
List<Integer> res = new ArrayList<>();
while(root!=null || !stk.isEmpty()){
//这里对应:inorderTraversalR(list,cur.left);
while(root!=null){
stk.push(root);
root = root.left;
}
//这里对应:list.add(cur.val);
root = stk.pop();
res.add(root.val);
//这里对应:inorderTraversalR(list,cur.right);
root = root.right;
}
return res;
}
中序遍历的流程是:
- 往左到最左
- 入结果
- 往右执行相同流程
其实这里有一个小问题,在下面的后序遍历会遇到,就是:
- 我们是否需要一个节点,来记录上一个位置?
其实是不需要的,因为我们是左-中-右的顺序,这意味着我们经过的中节点是不需要再遍历的了。
同理可得后序遍历的:
public static void postOrderTraversalR(List<Integer> list,TreeNode cur){
inorderTraversalR(list,cur.left);
inorderTraversalR(list,cur.right);
list.add(cur.val);
}
public static List<Integer> postOrderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) return res;
Deque<TreeNode> stack = new LinkedList<>();
TreeNode prev = null;
while(null!=root || !stack.isEmpty()){
//inorderTraversalR(list,cur.left);
while(null!=root){
stack.push(root);
root = root.left;
}
//list.add(cur.val);
root = stack.pop();
//
if(root.right==null || root.right==prev){
res.add(root.val);
//这里需要记录一下,因为我们没有像栈一样的本地记录可以恢复,因此我们要记录这个值,来确保不会进入死循环
prev = root;
root = null;
}else{
//inorderTraversalR(list,cur.right);
stack.push(root);
root = root.right;
}
}
return res;
}
后序遍历会相对麻烦一些,因为我们遍历的顺序是左右中,这就意味着我们需要记录一下我们访问的右节点,否则会陷入死循环。
这里不能理解的话也没关系,可以先记下来再慢慢消化。
\