迭代法
前序遍历
比较简单,注意入栈是右子节点先,后左节点,毕竟左结点马上要出栈,结束条件就是栈空
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null){
return res;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.pop();
res.add(node.val);
if(node.right != null){
stack.push(node.right);
}
if(node.left != null){
stack.push(node.left);
}
}
return res;
}
}
中序遍历
中序和后序要注意节点出栈到底意味着什么,中序意味着其左子树访问完全,后序在中序基础改进,也就需要多一步右子树的判断,如果不符合,还要将其放回栈中。
左子结点不断入栈,出栈的时候说明其左子树已经遍历完了,这个节点可以将其值加入res,后续将指针指向右子节点,继续循环
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
//栈空了也不能结束的,root节点被排出,此时为空,但是右子树还没遍历
while(cur != null || !stack.isEmpty()){
while(cur != null){
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
res.add(cur.val);
cur = cur.right;
}
return res;
}
}
后序遍历
栈遍历版本: 建议先做中序遍历,后序只是在中序上多了一些操作。
与中序的不同之处在于:
中序遍历中,从栈中弹出的节点,其左子树是访问完了,可以直接访问该节点,然后接下来访问右子树。
后序遍历中,从栈中弹出的节点,我们只能确定其左子树肯定访问完了,但是无法确定右子树是否访问过。
因此,我们在后序遍历中,引入了一个prev来记录历史访问记录。
当访问完一棵子树的时候,我们用prev指向该节点。
这样,在回溯到父节点的时候,我们可以依据prev是指向左子节点,还是右子节点,来判断父节点的访问情况。
class Solution{
public List<Integer> method1(TreeNode root) {
List<Integer> ans=new LinkedList<>();
Stack<TreeNode> stack=new Stack<>();
TreeNode prev=null;
//主要思想:
//由于在某颗子树访问完成以后,接着就要回溯到其父节点去
//因此可以用prev来记录访问历史,在回溯到父节点时,可以由此来判断,上一个访问的节点是否为右子树
while(root!=null||!stack.isEmpty()){
while(root!=null){
stack.push(root);
root=root.left;
}
//从栈中弹出的元素,左子树一定是访问完了的
root=stack.pop();
//现在需要确定的是是否有右子树,或者右子树是否访问过
//如果没有右子树,或者右子树访问完了,也就是上一个访问的节点是右子节点时
//说明可以访问当前节点
if(root.right==null||prev==root.right){
ans.add(root.val);
//更新历史访问记录,这样回溯的时候父节点可以由此判断右子树是否访问完成
prev=root;
root=null;
}else{
//如果右子树没有被访问,那么将当前节点压栈,访问右子树
stack.push(root);
root=root.right;
}
}
return ans;
}
}
自己的版本
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
TreeNode pre = null;
while(cur != null || !stack.isEmpty()){
while(cur != null){
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
if(cur.right == null || pre == cur.right){
res.add(cur.val);
//更新记录上一个节点
pre = cur;
//否则循环会再次其左子树遍历
cur = null;
}else{
//右子树还没遍历,还不是你这个节点出栈的时机,还要把你放回去
stack.push(cur);
cur = cur.right;
}
}
return res;
}
}
Morris法
代码
DFS迭代或者递归都是用到了栈;BFS是会用到队列。
如果使用M o r r i s 遍历就不需要了, 他的实现过程其实就是把叶子节点的指针给利用起来, 因为一般情况下, 二叉树的叶子节点是没有子节点的, 也就是说他们是指向空, 这样总感觉有点浪费, M o r r i s 的实现原理其实就是把叶子节点的指针给利用了起来。
中序遍历,将左子树的最右节点的右指针指向root。
输出情况分为两种,一种是左孩子为空,另一种是左子树已经遍历完成,此时也就可以遍历右子树了
刚开始,找到左子树的最右节点后将他右指针指向root,后root= root.next,等到左孩子为空的时候,就要输出了,并且指向右孩子,这个右孩子不就是我们刚开始的节点,然后再来看他左子树的最右节点,如果发现已经指向了自己,说明已经遍历过了,将其指针取消,并且输出以及开始他的右子树。
对节点内容的操作,也就是访问右孩子那一步
public static void morrisIn(Node head) {
if(head == null){
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null){
mostRight = cur.left;
if(mostRight != null){
while (mostRight.right !=null && mostRight.right != cur){
mostRight = mostRight.right;
}
if(mostRight.right == null){
mostRight.right = cur;
cur = cur.left;
continue;
}else {
mostRight.right = null;
}
}
System.out.print(cur.value+" ");
cur = cur.right;
}
System.out.println();
}