二叉树遍历问题(js)

掌握二叉树的遍历问题是解决二叉树各类问题的基础。

遍历类型

  1. 前序遍历
  2. 中序遍历
  3. 后序遍历

前序遍历为先遍历根节点,即中左右
中序遍历为第二遍历根节点,即左中右
后序遍历为最后遍历根节点,即左右中

本文主要是以解决leetcode上的题为目的,所以这里就不介绍上面三种遍历具体概念。解决上面这三种遍历的主要有两个方法,递归法和迭代法。

递归法

前序

		var preorderTraversal = function(root) {
            var res = [];
            qianxu(root);

            function qianxu(node) {
                if (!node) {
                    return;
                }
                res.push(node.val);
                if (node.left) qianxu(node.left);
                if (node.right) qianxu(node.right);
            }
            return res;
        };

中序

		var inorderTraversal = function(root) {
            var res = [];

            function zhongxu(node) {
                if (!node) {
                    return;
                }
                if (node.left) zhongxu(node.left);
                res.push(node.val);
                if (node.right) zhongxu(node.right);
            }
            zhongxu(root);
            return res;
        };

后序

		 var postorderTraversal = function(root) {
            var res = [];
            houxu(root);

            function houxu(node) {
                if (!node) {
                    return;
                }
                if (node.left) houxu(node.left);
                if (node.right) houxu(node.right);
                res.push(node.val);
            }
            return res;
        };

我们可以看出,三种遍历方式的递归法的唯一区别就是处理node节点的位置,相信熟悉递归的同学都额能够很好的理解这个递归法。

迭代法

迭代法是通过配合栈的先进后出来实现的。
前序

		var preorderTraversal = function(root) {
            if (!root) {
                return [];
            }
            var res = [];
            var stack = [];
            stack.push(root);
            while (stack.length) {
                var node = stack.pop();
                res.push(node.val);
                if (node.right) stack.push(node.right);
                if (node.left) stack.push(node.left);
            }
            return res;
        };

前序的遍历顺序是中左右,所以我们需要让右节点先进栈,左节点后进站,因为栈是先进后出,所以左节点先出栈保存到数组中,然后再是右节点,这样才符合我们前序遍历的遍历规则。
后序

var postorderTraversal = function(root) {
            if (!root) {
                return [];
            }
            var res = [];
            var stack = [];
            stack.push(root);
            while (stack.length) {
                var node = stack.pop();
                res.push(node.val);
                if (node.left) stack.push(node.left);
                if (node.right) stack.push(node.right);
            }
            res.reverse();
            return res;
        };

相信有的同学看了后序遍历之后,感觉就是前序遍历取反的结果,对,我们这里确实采用了这个思路,不过我们要注意后序遍历的遍历顺序是左右中,但我们这里进入数组的方式是中右左,然后再取反。所以我们需要让左节点先进栈,然后再是右节点。有的同学可能会说那中序遍历是不是也是在这两种遍历的基础稍微改动就行了呢?下面我们先把中序遍历的代码贴出。
中序

 		var inorderTraversal = function(root) {
            if (!root) {
                return [];
            }
            var res = [];
            var stack = [];
            var cur = root;
            while (cur || stack.length) {
                if (cur) {
                    stack.push(cur);
                    cur = cur.left;
                } else {
                    var node = stack.pop();
                    res.push(node.val);
                    cur = node.right;
                }
            }
            return res;
        };

其实中序遍历的写法和前两种遍历的写法还是有一定的不同,原因是无论是前序遍历还是后序遍历,他们的遍历顺序和处理顺序都是在同一时候(也就是说一边遍历就能一边操作节点),但中序遍历不行,中序遍历最先处理的节点是最左边最下面的节点,所以我们需要定义一个指针来帮助我们遍历,也就是我们上面的cur。

到这里,肯定有的同学会说,这三种方法也太麻烦了,有没有一种写法只需要改变一点,就能同时实现三种遍历呢,答案是肯定的。

迭代法的统一写法

前序

//前序
        // 前序遍历:中左右
        // 压栈顺序:右左中

        var preorderTraversal = function(root, res = []) {
            const stack = [];
            if (root) stack.push(root);
            while (stack.length) {
                const node = stack.pop();
                if (!node) {
                    res.push(stack.pop().val);
                    continue;
                }
                if (node.right) stack.push(node.right); // 右
                if (node.left) stack.push(node.left); // 左
                stack.push(node); // 中
                stack.push(null);
            };
            return res;
        };

中序

//中序
        //  中序遍历:左中右
        //  压栈顺序:右中左

        var inorderTraversal = function(root, res = []) {
            const stack = [];
            if (root) stack.push(root);
            while (stack.length) {
                const node = stack.pop();
                if (!node) {
                    res.push(stack.pop().val);
                    continue;
                }
                if (node.right) stack.push(node.right); // 右
                stack.push(node); // 中
                stack.push(null);
                if (node.left) stack.push(node.left); // 左
            };
            return res;
        };

后序

 //后序
        // 后续遍历:左右中
        // 压栈顺序:中右左

        var postorderTraversal = function(root, res = []) {
            const stack = [];
            if (root) stack.push(root);
            while (stack.length) {
                const node = stack.pop();
                if (!node) {
                    res.push(stack.pop().val);
                    continue;
                }
                stack.push(node); // 中
                stack.push(null);
                if (node.right) stack.push(node.right); // 右
                if (node.left) stack.push(node.left); // 左
            };
            return res;
        };

这三种统一的写法,实际上它牺牲了代码的可读性为代价,所以我不太建议各位去用这种写法,这里就不多介绍了,具体原理是在已经遍历但未处理的节点后面加个null,有兴趣的同学可以去实践一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值