javascript 算法之 树 必出精品 (递归版本简单 非递归版本面试很重要)(一)

867 阅读3分钟

ps:这部分 视频可在 b战和知乎看到

1、介绍

2、深度优先遍历 简写 dfs

  • 那么怎么进行 `深度优先 遍历呢 `` ?

  • 上代码 尝试 实现一下

    // 新建树 利用 Object和Array
    var tree = {
        val: 'a',
        children: [{
                val: 'b',
                children: [{
                        val: 'd',
                        children: []
                    },
                    {
                        val: 'e',
                        children: [],
                    }
                ],
            },
            {
                val: 'c',
                children: [{
                        val: 'f',
                        children: []
                    },
                    {
                        val: 'g',
                        children: [],
                    }
                ],
            }
        ]
    }

    // 深度优先遍历   简写 dfs
    var dfs = function (root) {
        // 1、访问根节点
        console.log(root.val); // a b  d  e c f g
        // 2、对根节点的 children挨个进行深度优先遍历
        // root.children.forEach((child) => {
        //     dfs(child)
        // })
        
        // 遍历第二种方式 
        dfs(n.left)
        dfs(n.right)
        //简化一下代码
        root.children.forEach(dfs)
    }

    var test = dfs(tree)
  • 遍历结果 是可预期的

3、广度优先遍历 简写 bfs

  • 这个又应该怎么操作一下 ?

  • 上代码 注意 此处的 tree 就是 上面的tree

    // 新建树 利用 Object和Array
    var tree = {
        val: 'a',
        children: [{
                val: 'b',
                children: [{
                        val: 'd',
                        children: []
                    },
                    {
                        val: 'e',
                        children: [],
                    }
                ],
            },
            {
                val: 'c',
                children: [{
                        val: 'f',
                        children: []
                    },
                    {
                        val: 'g',
                        children: [],
                    }
                ],
            }
        ]
    }

    // 简写 bfs 
    var bfs = function (root) {
    	if(!root) {return 0}
        // 1、新建队列
        const q = [root]
        while (q.length) {
            // 2、 队头出队 并访问
            const n = q.shift()
            console.log(n.val)
            // 3、队头 children 入队
            n.children.forEach((child) => {
                q.push(child)
            })
            //遍历第二种方法
            //if(n.left) q.push(n.left)
            //if(n.right) q.push(n.right)
        }
    }


    // test 
    var test = bfs(tree)

  • 结果也是可以 预期的 ,可预期性 可是个好的趋势哦

4、二叉树

5、二叉树先序(前序)遍历

  • 这一部分非常重要 所以分开 方便展示

  • 话不多说 上代码 展示一下这个事情

  • 不要着急 我们先建立一个二叉树 方便 后续 展示这三种遍历方式

  • 磨刀不误砍柴功(我是真的砍过柴 磨过刀)

  • 先种下(新建) 一棵二叉树 新建文件二叉树.js

const bt = {
  val: 1,
  left: {
    val: 2,
    left: {
      val: 4,
      left: null,
      right: null,
    },
    right: {
      val: 5,
      left: null,
      right: null,
    },
  },
  right: {
    val: 3,
    left: {
      val: 6,
      left: null,
      right: null,
    },
    right: {
      val: 7,
      left: null,
      right: null,
    },
  },
};

// module.exports = bt;

  • 这是一个非常简单的模型 不过为了方便大家看这个模型 我把它画出来
  • 使用 它并实现 先序遍历
<!-- 引入 新建的二叉树  因为这是一个html文件-->
<!-- 如果这个地方新建的是一个js文件 直接 const bt = require('./二叉树.js') -->
<script src="./3.二叉树.js"></script>
<script>
    var pre = function (root) {
        // 防错 节点不为空 return 不再往下走
        if (!root) {
            return;
        }
        console.log(root.val)
        pre(root.left)
        pre(root.right)
    }

    // 测试一下 
    var test = pre(bt)
</script>

  • 结果 符合预期 非常nice

5、二叉树中序遍历

  • 我相信你还不过瘾 那我们再看看中序遍历

  • 代码演示一下 这个过程会很明显

<!-- 引入 新建的二叉树  因为这是一个html文件-->
<!-- 如果这个地方新建的是一个js文件 直接 const bt = require('./二叉树.js') -->
<script src="./3.二叉树.js"></script>
<script>
    var ino = function (root) {
        if (!root) {
            return
        }
        ino(root.left)
        console.log(root.val)
        ino(root.right)
    }

    //test
    var test = ino(bt)
</script>

  • 结果也是可以预期的呢

  • 非常好 这个时候 你已经学会了 前序 和 中序遍历的方式

6、二叉树后序遍历

  • 话不多说 我们 赶紧上代码演示一下
<!-- 引入 新建的二叉树  因为这是一个html文件-->
<!-- 如果这个地方新建的是一个js文件 直接 const bt = require('./二叉树.js') -->
<script src="./3.二叉树.js"></script>
<script>
    var bed = function (root) {
        if (!root) {
            return
        }
        bed(root.left)
        bed(root.right)
        console.log(root.val)
    }

    // test 
    var test = bed(bt)
</script>


  • 预期结果 在我们控制之中 很nice

  • 可以去转一圈了 (不是摸鱼)

7、先序遍历 非递归版本 使用栈

  • 标题也说了 非递归版本 面试特别重要 需要学会
  • 代码演示一下 怎么解决问题 ?
<!-- 引入 新建的二叉树  因为这是一个html文件-->
<!-- 如果这个地方新建的是一个js文件 直接 const bt = require('./二叉树.js') -->
<script src="./3.二叉树.js"></script>
<script>
    // 递归版本
    /*
    var pre = function (root) {
        // 防错 节点不为空 return 不再往下走
        if (!root) {
            return;
        }
        console.log(root.val)
        pre(root.left)
        pre(root.right)
    }  */

    // 非递归版本
    var pre = function (root) {
        if (!root) {
            return;
        }
        // 新建一个栈里面放的是根节点
        var stack = [root]
        while (stack.length) {
        // 弹出根节点  并访问根节点
            var n = stack.pop()
            console.log(n.val)
            // 栈是后进先出的结构  先推入右节点 后推入左节点
            if (n.right) stack.push(n.right)
            if (n.left) stack.push(n.left)
        }
    }

    // 测试一下 
    var test = pre(bt)
</script>

  • 先序遍历相当于 从前往后看书 结果也是可预期的

8、中序遍历 非递归版本 使用栈

  • 中序遍历 你是否还记得呢 ?
  • 不记得 请往上看看就行
<!-- 引入 新建的二叉树  因为这是一个html文件-->
<!-- 如果这个地方新建的是一个js文件 直接 const bt = require('./二叉树.js') -->
<script src="./3.二叉树.js"></script>
<script>
    //递归版本
    /*
    var ino = function (root) {
        if (!root) {
            return
        }
        ino(root.left)
        console.log(root.val)
        ino(root.right)
    }
    */

    // 非递归版本
    var ino = function (root) {
        if (!root) {
            return
        }
        // 新建栈
        const stack = []
        //新建指针
        var p = root
        while (stack.length || p) {
            while (p) {
                //将左节点放入栈中
                stack.push(p)
                p = p.left
            }
            //访问根节点
            const n = stack.pop()
            console.log(n.val)
            //将右节点放入栈中
            p = n.right
        }
    }


    //test
    var test = ino(bt)
</script>

9、后序遍历 非递归版本 使用栈

  • 有个技巧 我们发现后续遍历 递归版本是这样的 左---》右 ---》根
递归版本
    var bed = function (root) {
        if (!root) {
            return
        }
        bed(root.left)
        bed(root.right)
        console.log(root.val)
    }
  • 先序遍历的版本是这样的 根--》左---》右
var pre = function (root) {
        // 防错 节点不为空 return 不再往下走
        if (!root) {
            return;
        }
        console.log(root.val)
        pre(root.left)
        pre(root.right)
    } 
  • 实现技巧 在于 将后序遍历 倒过来 然后复用先序遍历的代码
<!-- 引入 新建的二叉树  因为这是一个html文件-->
<!-- 如果这个地方新建的是一个js文件 直接 const bt = require('./二叉树.js') -->
<script src="./3.二叉树.js"></script>
<script>
    // 递归版本
    // var bed = function (root) {
    //     if (!root) {
    //         return
    //     }
    //     bed(root.left)
    //     bed(root.right)
    //     console.log(root.val)
    // }


    // 非递归版本
    var bed = function (root) {
        if (!root) {
            return
        }
        //新建两个栈
        const stack = [root]
        const outputStack = []
        while (stack.length) {
            // 访问根节点
            const n = stack.pop()
            // 根节点放入输出栈中
            outputStack.push(n)
            // 分别对左节点和右节点入栈操作 这个刚好和先序遍历相反
            if (n.left) stack.push(n.left)
            if (n.right) stack.push(n.right)
        }
        // 将结果出栈
        while (outputStack.length) {
            var n = outputStack.pop()
            console.log(n.val)
        }
    }

    // test 
    var test = bed(bt)
</script>

10、小结

  • 先序遍历 根 --》 左 ---》 右 注意在非递归栈中 根 右 左
  • 中序遍历 左 --》 根 ---》 右
  • 后序遍历 左 --》 右 ---》 根 注意使用 先序遍历复用
  • 看到这里 可以和作者一起喝一杯 卡布奇诺 欢迎来