算法学习——二叉树的深度优先遍历

209 阅读6分钟

前言

二叉树主要有两种遍历方式:深度遍历和广度遍历,其中深度优先遍历分为:

  • 前序遍历
  • 中序遍历
  • 后序遍历 每种遍历又有递归法和迭代法两种,而这里的前、中、后其实指的是中间节点的遍历顺序,例如:
  • 前序遍历:中左右
  • 中序遍历:左中右
  • 后序遍历:左右中 对照图片如果我们能顺利整理出正确的读取顺序,那么就算理解了

image.png

深度遍历我们一般采取递归的方式解决,但是其实栈就是递归的一种实现结构,所以深度优先遍历我们也可以借助栈而非递归的方式来实现。

另外二叉树的定义一般如下

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */

递归遍历

递归需要掌握三个必要条件

  1. 确定递归函数的输入和输出值:确定哪些值是要在递归中处理的,以及递归函数需要返回哪些值
  2. 确定递归的终止条件:递归要有终止递归的条件,也就是在逻辑中要定义满足什么条件后终止循环调用自身,不然无限制调用下去会内存栈溢出
  3. 确定递归的单层逻辑:确定每一层处理的过程也就是递归需要重复调用的逻辑

前序遍历

  1. 首先确定一下递归函数的参数以及返回值:首先要知道递归的节点值,另外需要一个数组用来储存递归的值,如果传入了数组用来储存递归的值,那么返回值的话就并不是那么重要了,可以选择把数组返回出来
/**
 * @param {TreeNode} root
 * @param {number[]} array
 *
 */
function traversal(root, array)
  1. 要确定终止条件:什么时候终止当前递归,那么就是当当前的root节点是空节点的时候,那么就不需要继续往节点内塞值,就可以终止当前递归了
if(root === null) return array;
  1. 确定当前递归的逻辑:前序遍历是中左右循环,所以在单层递归逻辑,要先取中节点数值,之后再递归左节点然后再递归右节点
array.push(root.val)
traversal(root.left, array)
traversal(root.right, array)

单层递归逻辑按照中左右,这样前序遍历就完成了

完整代码如下:

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */

/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var preorderTraversal = function (root) {
    let array = []
    return traversal(root, array)
};

/**
 * @param {TreeNode} root
 * @param {number[]} array
 * @return {number[]}
 */
function traversal(root, array){
    if(root === null) return array;
    array.push(root.val)
    traversal(root.left, array)
    traversal(root.right, array)
    return array
}

以此类推

中序遍历

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */

/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var inorderTraversal = function (root) {
    let array = []
    return traversal(root, array)
};

/**
 * @param {TreeNode} root
 * @param {number[]} array
 * @return {number[]}
 */
function traversal(root, array){
    if(root === null) return array;
    traversal(root.left, array)
    array.push(root.val)
    traversal(root.right, array)
    return array
}

后序遍历

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */

/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var postorderTraversal = function (root) {
    let array = []
    return traversal(root, array)
};

/**
 * @param {TreeNode} root
 * @param {number[]} array
 * @return {number[]}
 */
function traversal(root, array){
    if(root === null) return array;
    traversal(root.left, array)
    traversal(root.right, array)
    array.push(root.val)
    return array
}

迭代遍历

使用迭代的方法对二叉树进行深度遍历,和递归法会有所不同,但是同样会用到栈操作

前序遍历

我们知道前序遍历的顺序是中左右,所以我们第一步是取出来栈顶的节点取出值,然后再放进去节点的右子节点,最后再放节点的左子节点,先放右子节点再放左子节点的原因是因为,栈是先进后出,所以先放右再放左

代码如下

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var preorderTraversal = function (root) {
    let stack = []
    let result = []
    if (!root) return result
    stack.push(root)
    while (stack.length > 0) {
        const top = stack.pop()
        result.push(top.val)
        if (top.right) stack.push(top.right)
        if (top.left) stack.push(top.left)
    }
    
    return result
};

中序遍历

使用迭代法中序遍历的方式和前序遍历的方式并不一致,本质原因是因为迭代先访问的节点是中间节点,而前序遍历先操作的也是中间节点,所以前序遍历的迭代法,访问和操作的顺序是一致的,为什么中序遍历和前序遍历不一致呢?

中序操作迭代先访问的也是中间节点,但是中序操作需要先处理的是左子节点,所以就会造成访问和处理的数据不一致,这个时候我们可以用一个指针用来标记节点的左节点,当指针指向null的时候就证明到达最底部的左节点了,就可以开始向栈弹出,之后再将指针指向弹出的节点的右节点,再循环指向左子节点,最后就可以按照中序遍历的顺序遍历所有节点

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var inorderTraversal = function (root) {
    let stack = []
    let array = []
    let cur = root
    while (cur || stack.length > 0) {
        if (cur !== null) {
            stack.push(cur)
            cur = cur.left
        } else {
            const node = stack.pop()
            array.push(node.val)
            cur = node.right
        }
    }
    return array
};

后序遍历

后序遍历和前序遍历类似,只需要针对前序遍历的顺序稍加修改就可以结局,把前序遍历的中左右顺序改成中右左顺序,之后再整体翻转就可得出后序遍历的顺序

  • 前序遍历:中->左->右 ——》 中->右->左 ——》 左->右->中
  • 后序遍历:左->右->中
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var postorderTraversal = function (root) {
    let stack = []
    let array = []
    if (!root) return array
    stack.push(root)
    while (stack.length > 0) {
        const node = stack.pop()
        array.push(node.val)
        if (node.left) stack.push(node.left)
        if (node.right) stack.push(node.right)
    }
    return array.reverse()
};

统一迭代法

如上迭代法,前序遍历、中序遍历、后序遍历是三种不同的思路,那么如何用同一种思路解决上面的三种遍历呢,其实是有的,我们可以在遍历的过程中,在要取出来的节点前加上一个null的标记,这样当我们读到标记的节点时,就知道需要把后一个节点的数值取出来了

前序遍历

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var preorderTraversal = function (root) {
    let stack = []
    let result = []
    if (!root) return result
    stack.push(root)
    while (stack.length > 0) {
        const node = stack.pop()
        if (node !== null) {
            if (node.right) stack.push(node.right)
            if (node.left) stack.push(node.left)
            stack.push(node)
            stack.push(null)
        } else {
            const st = stack.pop()
            result.push(st.val)
        }
    }
    return result
};

中序遍历

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var inorderTraversal = function (root) {
    let stack = []
    let result = []
    if (!root) return result
    stack.push(root)
    while (stack.length > 0) {
        const node = stack.pop()
        if (node !== null) {
            if (node.right) stack.push(node.right)
            stack.push(node)
            stack.push(null)
            if (node.left) stack.push(node.left)
        } else {
            const st = stack.pop()
            result.push(st.val)
        }
    }
    return result
};

后序遍历

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var postorderTraversal = function (root) {
    let stack = []
    let result = []
    if (!root) return result
    stack.push(root)
    while (stack.length > 0) {
        const node = stack.pop()
        if (node !== null) {
            stack.push(node)
            stack.push(null)
            if (node.right) stack.push(node.right)
            if (node.left) stack.push(node.left)
        } else {
            const st = stack.pop()
            result.push(st.val)
        }
    }
    return result
};