94 Binary Tree Inorder Traversal 【递归和迭代的对比较分析】

一道很常规的二叉树遍历题,相信大家都在课上学习过。

但是题目要求是不能用递归调用的方法,也就是课上讲过的方法。要用iterative迭代的方法,也就是一个一个找,通过while循环来输出。

先把递归的方法代码写出来,如下。要注意的是,我连递归调用都不能立马想起来,只想到了当node是null时,什么都不用做。那么当不是null的时候呢?其实就是按照preorder的顺序,摆放的三行代码即可,怎么记忆呢?以单一root为例,就是先输出左子节点,其为空,则不处理,然后到本身,然后到右子节点。

课程中一般只需要打印,但是这题要求把所有节点数值收集到一个集合里,所以设计一个helper函数,不返回任何值 (void 函数在这里比较好用来递归调用)。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result= new ArrayList<>();
        helper(root, result);
        return result;
    }
    // 课程中一般只需要打印出来,但是这里需要返回一个list,那么就写一个helper函数,加入一个输入的参数即能解决问题。
    public void helper(TreeNode root, List<Integer> list){
/*        if(root!=null){
            helper(root.left, list);
            list.add(root.val);
            helper(root.right, list);
        }*/
        if(root=null) return; // 这句code其实并不是很intuitive,意思是一样的,如果为空,则不做任何处理,刚进入就返回。
        helper(root.left, list);
        list.add(root.val);
        helper(root.right, list);
    }
}

那么用迭代的方法,思路是比较有意思的,同时也比较固定,没有其他解法。

参考代码来理解整个逻辑:

1,从root开始要往深度走,找到最左边的节点,那么中间走过的这些节点都需要一次记录下来,而且顺序的话是要反向输出的。用stack合适!

2,一直往最左走到了null,那么stack中的top位置就是要弹出并且记入结果的node。然而这个node的右端是紧接着要读取的部分,所以curr要指向这个node的右子节点,然后开始重复上面的往左走到最左,并且一直往stack装node的过程。

大概的过程就是这样,其实本质上是什么?就是递归调用方法中,一模一样的过程,因为在递归调用中,这些访问过的节点也都是存放在系统中的stack里,一个个压入,然后当遇到return时再一个个弹出,只不过递归调用是把所有过程都隐藏起来了,而迭代的话,是显示的一一写出了。

代码中一个需要好好理解的就是第一个while的条件:当curr不为null,或者stack里还有node时,就一直循环操作。只有当curr指向null,同时stack里也没有东西,那么才代表整个树遍历完毕。在每一个循环中,curr还是变化的中心:1 往左走到底;2 指向最后左节点;3,指向最左节点的右子节点;然后再次进入循环。

以上的过程,最好是通过画图在脑海中形成印象:一棵树,从根到最左,到右子,依然到最左,如果没有,则指向stack中弹出来top element,等于是往上面爬了一层。具体画面看ppt中的图吧,文字也表示不清楚。

代码如下:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        
        Stack<TreeNode> stack = new Stack<>();
        TreeNode curr=root;
        while(curr!=null || !stack.isEmpty()){
            while(curr!=null){
                stack.push(curr);
                curr=curr.left;
            } // curr is null here after
            curr=stack.pop();
            result.add(curr.val);
            curr=curr.right;
        } // 想象一下最后curr的完结情况,最右端的节点,因此按照while循环,找到其最左子节点(null),然后pop弹出该节点
        // 写入result,curr指向右节点,为null,此时stack也为空,整个树遍历完毕,循环也结束。
        
        return result;
    }
}

最后加一个 递归调用,但不需要helper方法的解法,之前说了如果是有return类型的方法递归调用,那么可能不好操作,因为返回结果每次要new 一个list,但是其实把list里所有的数全部add进去也是可以的,就用addAll就行:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if(root==null) return result;
        result.addAll(inorderTraversal(root.left)); // 两步写作一行代码,有点意思,哈哈
        result.add(root.val);
        result.addAll(inorderTraversal(root.right)); // 同理
        return result;
    }
}

以上三组代码,性能效率是一样的,充分说明了之前的判断,recursive和iterative的本质是一样的,递归只是系统自建stack,而迭代是把建stack,压入,弹出这些过程全部显示的写成代码。

通过这题把二叉树的遍历算是搞的比较透彻。

之后两篇分析 preorder 和 postorder


#include <stdio.h>#include <stdlib.h>#include <string.h>/* 二叉树节点 */typedef struct TreeNode { char val; struct TreeNode *left; struct TreeNode *right;} TreeNode;/* 根据先序序列和中序序列构建二叉树 */TreeNode *buildTree(char *preorder, char *inorder, int preStart, int preEnd, int inStart, int inEnd) { // 先序序列为空,返回NULL if (preStart > preEnd) { return NULL; } // 创建根节点 TreeNode *root = (TreeNode *)malloc(sizeof(TreeNode)); root->val = preorder[preStart]; root->left = root->right = NULL; // 在中序序列中查找根节点的位置 int rootIndex; for (rootIndex = inStart; rootIndex <= inEnd; rootIndex++) { if (inorder[rootIndex] == root->val) { break; } } // 计算左子树的节点个数 int leftSize = rootIndex - inStart; // 递归构建左子树和右子树 root->left = buildTree(preorder, inorder, preStart + 1, preStart + leftSize, inStart, rootIndex - 1); root->right = buildTree(preorder, inorder, preStart + leftSize + 1, preEnd, rootIndex + 1, inEnd); return root;}/* 输出二叉树的后序序列 */void postorderTraversal(TreeNode *root) { if (root == NULL) { return; } postorderTraversal(root->left); postorderTraversal(root->right); printf("%c", root->val);}int main() { char preorder[] = "ABDEGCHF"; char inorder[] = "DBEGAHCF"; // 构建二叉树 TreeNode *root = buildTree(preorder, inorder, 0, strlen(preorder) - 1, 0, strlen(inorder) - 1); // 输出二叉树的后序序列 printf("The postorder traversal of the binary tree is: "); postorderTraversal(root); printf("\n"); return 0;}
最新发布
06-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值