tree-inorder中序遍历

tree的中序遍历“左-根-右”。看了我94题的struggle历程,你在生活中大概会变得更宽容哈哈哈哈,对你智商捉急的小伙伴。他们从A–>E 必须经过A,B,C,D,才能到E,有时候还得A,B,C,F,G,H,I,J,K…D,才能到E。呵呵呵呵。: )

题目简介
94. Binary Tree Inorder Traversal基本的Inorder-Traversal
285. Inorder Successor in BST求某一点的Inorder后继
173. Binary Search Tree IteratorIterator:调用一次访问一个
510. Inorder Successor in BST II求某一点的Inorder后继,不给root

94. Binary Tree Inorder Traversal

在这里插入图片描述
iterative的思路

  1. “左根右”,如果左子树不为空,就一直向左,路上经过的节点都暂时不访问,因为“左-根”。暂时不访问的节点都放入栈里。
  2. 等到了leftmost,没有左孩子了,就可以访问当前节点了,“根”。
    注意!我原来是遍历到leftmost,leftmost就不入栈了,直接访问。但这样做的缺点是:让这个leftmost节点变得很特殊,然而其实它完全可以合并进普通的节点的处理。应该让这个leftmost也入栈。等go-left结束后,直接pop出一个来,就可以访问“根”了。
  3. 当前节点访问完,就该访问“右”了,检查一下有没有右 –>不需要
    (下面划掉的就是我原来的思路,留在这里为了说明原来思路冗余在哪里。)
  • 在“有右”的情况:右孩子也可能有他的孩子,还要遵循in-order的访问规则,就往右走一步,然后进入下一轮。

  • 在“无右”情况:简单说就接着从栈里往外pop。
    这两种请问可以合并为一种,就都假装“有右”,然后进入下一轮即可。即便没有右,再多进行一次go-left也无伤大雅啦,因为此时是null,不会进入go-left,然后就又到了从栈里往外pop这一步,正好符合我们的需要。
    (我原来那个划掉的也AC了,但受到173写iterator那个题的启发,才改成了现在这个简单的不冗余的嘿嘿。)

    • 有右:直接访问右。注意此处只是往右走一步,因为右孩子也可能有他的孩子,还要遵循in-order的访问规则。往右走一步之后,就结束当前这一轮(外层)while-loop,进入下一轮的while-loop(不断向左)。
    • 无右:则这一层已经访问结束,需要从栈里弹出上一层的parent了。当前访问过的整个这一坨,都可以看做parent的左孩子。弹出的parent就是“根”了,这个虽然是一个新的没访问过的“根”,但它是在他左子树访问结束后才来到的,所以它已经是“可以立即访问”的状态,于是我们就访问它,“左-根”,又该检查有没有右了。(又回到3。如果在3里面不断到3.2,就一直在3–>3.2–>3–>3.2……,直到进入3.1,才能跳出这个local的小while-loop。)
  1. 然后就来到了一个“新的没访问过的根节点”,就可以结束这一轮(外层)while-loop,进入下一轮while-loop了。
    怎么理解外层的while-loop呢?一个“可访问的根节点”有三种(只有第三种是应该结束当前轮,进入下一轮的):
    1. 从栈里pop出来的:这种是它的左子树都访问完了,应该立即访问。访问完了还要go right一步,才能结束本层while-loop。
    2. 通过go left来到的点:go-left是用while-loop进行的(那个local while-loop我们不考虑,因为很明确下一步需要干嘛),所以肯定是leftmost的点,立即访问。 (改为:也放进栈里呵呵,保持一致性。)
    3. 通过go right来到的点: 这种就是彻底来到了一个新的“根”,就需要回到while-loop的下一轮。(这句话终于说对了,噗)
//iterative in-order
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ret = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (cur != null || !stack.empty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            cur = stack.pop();
            ret.add(cur.val);
            cur = cur.right;
        }
        return ret;
    }
}

recursive的就很简单了,直接地“左-根-右”即可。

//recursive in-order
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        inorder(result, root);
        return result;
    }
    
    private void inorder(List<Integer> result, TreeNode root) {
        if (root == null) {return;}
        inorder(result, root.left);
        result.add(root.val);
        inorder(result, root.right);
    }
}

285. Inorder Successor in BST

在这里插入图片描述

找p点的in-order后继节点

我自己写的思路:在普通的in-order traversal中,添加一个显示状态的flag,一旦找到p点,就把flag标记为“已找到p,请在下一个节点处返回”的状态found。这样,访问到每个节点的时候,都先检查这个found是否为true,来决定什么时候返回。

这种遍历tree的问题,我总有一种按pause button的冲动。

  • 比如这个题285希望访问到p之后能暂停住,然后输出下一个;
  • 173.Binary Search Tree Iterator,我也有点希望能让我发一个命令next(),走一步,中间访问到哪里了有个probe可以被记录,随时query……

然而这种冲动是不对的,一般是不容易实现的。这个题285可以用found的flag来标记是否访问到了我们要的节点的前驱,而173就不行了。这种遍历tree的算法,我们认为一旦开始,就会马不停蹄地运转直至结束,并不能插什么probe。

285这个题,上面的用普通in-order traversal进行改良的方法并没有利用BST的有序的性质。用了BST性质就有挺简单的思路:我们其实只是想找比要找的节点p值大的那个最接近的节点。基本上就是先找p。
用iterative的思路(这个非常像recursive的思路,但是它用了一个外层while-loop来维护“一层一层往下”这个过程):

  • 如果p的值比当前root值大,那要找的那个节点肯定在右子树,于是分派给右孩子接着找。
  • 如果p的值比当前root值小,那要找的那个节点,或者在左子树,或者是当前的root。于是我们先用result=root.val把root这个值保留下来作为可能的candidate,然后分派给左孩子接着找。
    • 如果下一层p值还是比当前的root(原来的root.left)小,则刚才保留的那个result=root.val就没用了,于是更新成result=现在的root.val。
    • 如果下一层p值比当前的root大,那就不更新result,往右孩子走。然后没准一直一直往右孩子走,到尽头了,发现最开始保存的那个result才是要求的节点,刚好就返回result。
//iterative
class Solution {
    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        TreeNode result = null;
        while(root!=null) {//"一层一层往下"
            if(p.val < root.val) {//只有比root小,往左走的时候,才更新result
                result = root;
                root = root.left;
            } else {//若比root值大,则肯定在右子树
            	root = root.right;
            }
        }
        return result;
    }
}

还有一个总结用recursive来求BST前驱和后驱的jeantimex的答案,和上面思路类似。回头空了再来展开讨论这个吧

173. Binary Search Tree Iterator

在这里插入图片描述

实现一个BST的iterator。每次调用next(),返回下一个值。
next() and hasNext() should run in average O(1) time and uses O(h) memory, where h is the height of the tree.

上面的题里我说,这种遍历tree的问题,我总有一种按pause button的冲动。然而,这种遍历tree的算法,我们认为一旦开始,就会马不停蹄地运转直至结束,并不能插什么probe。

如果想访问任意中间结果,就需要一个buffer在这些值产生的当下立即记录下来,即,用一个数组把tree in-order traversal记录下来,然后在调用next()的时候,依次输出。感觉Deque可以实现。但这样的空间复杂度是O(n),题目要求O(h)。说明不能把所有的节点都存到buffer里。

只保留左孩子们就好。因为即使在普通的in-order traversal中,右孩子也从来没有入栈过,通过栈里的节点弹出,找弹出的这个节点的右孩子,就可以找到右孩子们。而且go right一步,就又应该go-left while-loop,直到leftmost。

next()得到下一个节点的途径有三种:

  • go left: 不可能,因为先“左”后“根”。
  • go right: 可以合并进“从栈中弹出”。因为go right一步并不访问。还需要先把它的go-left while-loop所有节点都入栈,等栈里的这些go-left都出栈,才能轮到访问它。
  • go parent: 从栈中弹出

所以三种情况合并为一:next(),就是stack.pop()。除此以外的内部处理,就是pop出来的这个节点,要把它的go-left while-loop所有节点都入栈。

虽然大致思路如此,但我一直觉得counter-intuitive,于是想是不是自己94题那个基本的in-order traversal用的方法不是很好,回去看了看答案,发现果然是这样呵呵。(现在回到94把代码和思路都改好了^ _ ^)。94如果按照改好的思路做,则这个题就很容易想清楚。

class BSTIterator { //in-order
    
    Stack<TreeNode> stk = new Stack<>();

    public BSTIterator(TreeNode root) {
        pushAll(root);
    }
    
    public int next() {
        TreeNode cur = stk.pop();
        pushAll(cur.right);
        return cur.val;
    }
    
    public boolean hasNext() {
        return !stk.isEmpty();
    }
    
    private void pushAll(TreeNode node) {
        while (node != null) {
            stk.push(node);
            node = node.left;
        }
    }
}

510. Inorder Successor in BST II

求BST中某个节点的后继节点。不给tree root,而是给一个node,它有指向Parent的reference。
class Node {
public int val;
public Node left;
public Node right;
public Node parent;
}

“左-根-右”,先想当前节点到哪一步了。访问完当前节点,下一步不可能是“左”,因为先左才根。所以下一步只可能是:1)往右子树方向;2)往parent方向。

  • 如果存在右孩子:那下一步就是右子树里最小的那个
  • 如果没有右孩子:那就往parent方向走,找到某个ancestor,当前访问过的这一坨子树是他的左孩子,那就说明它还没被访问,下一个可以访问它了。如果是某个ancestor的右孩子,说明当前的这个ancestor已经被访问过了。就还要接着往上找ancestor。

注意:比如最右边的叶子节点这种,会进入“没有右孩子”那个condition,然后就从叶子节点一路往上找,始终都遇不到“我是他左孩子”的parent(因为都是由孩子),于是最后会temp.parent==null,跳出那个while-loop,最后返回temp.parent,刚好是null。

class Solution {
    public Node inorderSuccessor(Node node) {
        if (node.right != null) {//有右孩子:返回右子树的最小值
            Node temp = node.right;
            while (temp.left != null) {
                temp = temp.left;
            }
            return temp;
        } else if (node.parent != null) {//没右孩子:返回第一个“我是他左孩子”的parent
            Node temp = node;
            while (temp.parent != null && temp.parent.right == temp) {
                temp = temp.parent;
            }
            return temp.parent;
        } else {//never hit
            return null;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值