递归遍历
说说我对代码随想录里的三要素的理解:
1. 确定递归函数的参数和返回值:
因为这题是要返回一个含有后序遍历出来的结果集合,肯定是先创建一个result结果集,然后把每一次按后序遍历到的元素add到这个集合中去,那么,显然“add”是要操作这个集合,所以,需要把集合作为参数传入,而遍历这棵树,树的每一个节点肯定是要的,也作为参数,故递归函数有两个参数:集合(List<Integer> result),树节点(TreeNode root)。
2. 确定终止条件:
显然,走到节点为空时,就是终止的时候,那就是root == null时。
3. 确定单层递归的逻辑:
根据1,我们确定了我们的函数是:posTraversal(TreeNode root, List<Integer> result)
单层的递归逻辑就是“往集合中加入值”,是按“前/中/后”序的要求来选择先加入哪里的值。
这里是后序,那就是先去左孩子,把左孩子节点的val加入到集合中去,但是因为左孩子可能是一个分支,是分支的话,那么又对左分支进行后序遍历,所以递归调用posTraversal,这时参数是(root.left,result)。
同样逻辑,再去右孩子,把右孩子节点的val加入到集合中去,但是因为右孩子可能是一个分支,是分支的话,那么又对右分支进行后序遍历,所以再一次递归调用posTraversal,这时参数是(root.right,result)。
那么最后,我们再加入中间的节点,这时候不用担心了,中间的一定是节点,所以直接result.add(root.val);
最后根据三要素写出整体代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
posTraversal(root,result);
return result;
}
void posTraversal(TreeNode root, List<Integer> result) {
if(root == null) {
return;
}
posTraversal(root.left, result);
posTraversal(root.right, result);
result.add(root.val);
}
}
迭代遍历
前序遍历的迭代法就是先让根节点入栈(如果有根节点,没有则直接返回空的result),然后当栈不为空时,循环迭代判断,用一个临时树节点temp保存栈顶元素,然后访问栈顶元素,并且出栈(这就是“前”),之后,根据temp(此时还在“前”的位置上),先判断其右孩子是否为空,不为空,则先入栈(因为前序遍历是“前左右”,而栈的弹出是从栈顶,也就是后进的才会先出,为了访问完“前”,再访问“左”,就必须后让“左”入栈,之后栈顶的就是“左”了),之后判断左孩子是否为空,不为空则将左孩子入栈。这样,一个前序遍历的迭代法就完成了。
前序遍历迭代法:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> temp = new Stack<>();
if(root == null){
return result;
}
temp.push(root);
while(!temp.isEmpty()) {
TreeNode t = temp.pop();
result.add(t.val);
if(t.right != null) {
temp.push(t.right);
}
if(t.left != null) {
temp.push(t.left);
}
}
return result;
}
}
最难的莫过中序遍历了,中序遍历也有前序遍历的影子,也有一个中间变量来辅助入栈和访问,但他更像是一个链表中的指针,叫它cur,通过指针cur的移动,不停的遍历左子树,知道左子树为空了为止,遍历左子树的同时,将遍历过的左子树上的节点都入栈,当左子树为空时,即cur为空时,让cur回到栈顶的元素上,cur第一次为空时,栈顶元素一定是这棵树最底层最左边的节点,此时访问它,并让栈顶弹出,此时新的栈顶元素就是“中”,令cur指向cur的右孩子,别忘了还有访问右孩子,可能描述起来还是有点抽象,还是得看代码,因为会涉及到弹出栈这种回溯操作,还是有点难想象的,建议画图理解吧。
中序遍历迭代法:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root; // cur所指,即为要压入栈中的节点,当cur为空时,则表示要访问(将栈顶元素加入集合中)栈顶元素并将其弹出栈,接着让cur访问右子树
while(cur != null || !stack.isEmpty()) {
if(cur != null) {
stack.push(cur);
cur = cur.left;
}else {
cur = stack.peek(); // 取出栈顶,栈顶一定是刚刚入栈的“中”
result.add(cur.val); // 接下来访问
stack.pop(); // 弹出栈,表示访问完毕
cur = cur.right; // 别忘了右子树。如果右子树为null了,说明要回溯了,那等等还会进入到else中,栈顶即是要回溯到的第一个点
}
}
return result;
}
}
后续遍历就是在前序遍历的基础上修改就行,在入栈时,先让左孩子入栈再让右孩子入栈,这样就能得到一个“中右左”的结果集,然后把结果集逆转一下,就是后序遍历的“左右中”了。
后序遍历迭代法:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if(root == null) {
return result;
}
TreeNode temp = root;
stack.push(temp);
while(!stack.isEmpty()) {
temp = stack.pop();
result.add(temp.val);
if(temp.left != null) {
stack.push(temp.left);
}
if(temp.right != null) {
stack.push(temp.right);
}
}
Collections.reverse(result);
return result;
}
}
Collections.reverse(result):反转集合元素
统一迭代
统一的方法直接上代码随想录链接吧,先不看了。。。
代码:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new LinkedList<>();
Stack<TreeNode> st = new Stack<>();
if (root != null) st.push(root);
while (!st.empty()) {
TreeNode node = st.peek();
if (node != null) {
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
st.push(node); // 添加中节点
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
} else { // 只有遇到空节点的时候,才将下一个节点放进结果集
st.pop(); // 将空节点弹出
node = st.peek(); // 重新取出栈中元素
st.pop();
result.add(node.val); // 加入到结果集
}
}
return result;
}
}