js 算法之二叉树遍历

1,426 阅读4分钟

前言

今年相信很多小伙伴在面试过程中都遇到或多或少的算法题,既上一篇js算法之二分查找之后再推出一篇文章,二叉树的遍历。这两类型的题在面试中都很常见的,很多小伙伴不明白为什么前端也要考察二叉树,在阅读源码过程中有没有发现一些代码根本看不懂,比如vue,react中的DOM diff算法,为什么要涉及到树,堆,栈,链表,哈希表...如果你没有相关算法与数据结构经验的话阅读起来是相当困难的。

1、二叉树遍历

二叉树遍历又分为广度优先和深度优先,我们常说的中序、前序、后序一般指深度优先遍历。深度优先搜索(Depth First Search)是沿着树的深度遍历树的节点,尽可能深的搜索树的分支。广度优先搜索(Breadth First Search),又叫宽度优先搜索或横向优先搜索,是从根结点开始沿着树的宽度搜索遍历。

2、深度优先之前、中、后序遍历

有两种思想:递归和迭代,递归的思想比较简单,就是判断是否有左右节点,反复调用递归函数。

2.1、递归思想

/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var inorderTraversal = function(root) {
    const res = [];
    const helper = node => {
        if (!node) return;
        // 前、中、后序遍历的区别就在以下三行
        // 1、前序:根-->左-->右
        res.push(node.val);
        helper(node.left);
        helper(node.right);
        // 2、中序:左-->根-->右
        helper(node.left);
        res.push(node.val);
        helper(node.right);
        // 3、后序:右-->根-->左
        helper(node.right);
        res.push(node.val);
        helper(node.left);
    }
    helper(root);
    return res;
};

ES6的简单写法,以中序为例:

/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var inorderTraversal = function(root) {
    return root ? [...inorderTraversal(root.left), root.val, ...inorderTraversal(root.right)] : [];
};

2.2 迭代思想

以下是leetcode原题地址:
144.二叉树的前序遍历
94.二叉树的中序遍历
145.二叉树的后序遍历
思路:模拟栈,先进后出。由上面三序定义图解可以,对于二叉树任何一个节点而言,我们对它只有两种操作,一是将它作为根节点,在三序相应的遍历顺序中,取出其中的值作为结果集的一部分,二是继续探索它的左、右子树,按照三序的定义顺序来操作。所以我们可以在遍历过程中对任意一个节点附加一个标识,true:表示当前节点是三序遍历中相应顺序的根节点,碰到需要加入结果集,false:表示此节点属于三序遍历中的左、右子树的操作,需要压入栈中。

/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var postorderTraversal = function(root) {
    let res = [];
    if (!root) return res;
    let stack = [[root, false]];
    while(stack.length > 0) {
        const node = stack.pop();
        let curr = node[0];
        let isTrue = node[1];
        if (isTrue) {
            res.push(curr.val);
        } else {
        // 前、中、后序区别还是在以下代码,这个顺序要和上面的递归顺序相反
        // 前:右--->左--->根
        // 中:右--->根--->左
        // 后:根--> 右--->左
            if(curr.right){
                stack.push([curr.right,false]);
            }
            if(curr.left){
                stack.push([curr.left,false]);
            }
            stack.push([curr,true]);
        }
    }
    return res;
};

3、广度优先之二叉树层序遍历

102.二叉树的层序遍历
广度优先使用队列思想处理,1、将一层记录在数组中 并记录数组长度2、找下一行所有数据3、将数组首位弹出 将首位的左右节点追在数组后4、按照记录的数组长度 将上层的结点全部弹出后 此时数组只剩下下一行结点了 此时就完成了一层的遍历。同样,还是有迭代和递归两种办法:

递归

/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var levelOrder = function(root) {
    var res = [];
    if (!root) return res;
    helper(root, 0, res);
    return res;
};
function helper(root, level, res) {
    if (res.length === level) {
        res[level] = [];
    }
    res[level].push(root.val);
    if (root.left) helper(root.left, level + 1, res);
    if (root.right) helper(root.right, level + 1, res);
}

迭代

/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var levelOrder = function(root) {
    if(!root) return[];
    let res = [], queue = [root]
    while(queue.length) {
        let arr =[], temp = [];
        while(queue.length) {
            let curr = queue.shift();
            arr.push(curr.val);
            if (curr.left) temp.push(curr.left);
            if (curr.right) temp.push(curr.right);
        }
        queue = temp;
        res.push(arr)
    }
    return res;
};

ennn,到此就告一段落了,当然二叉树的遍历还有很多方法,但是最常用的就是以上几种,主要考查的是递归、栈、队列的思想,熟练掌握会发现有很多题都可以用这三种方法来解。好了,小伙伴们练起来吧,多练习,祝各位看官早日diaoda面试官~