一文图解树结构及应用

347 阅读4分钟

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

什么是树?

  • 一种分层数据的抽象模型
  • 常见的数结构: DOM树、级联选择、树形控件......
  • js中,可以用object, array构建树。

例:

{
    value: "zhe jiang",
    label: "zhe jiang",
    children: [
        value: "hangzhou",
        label: "hangzhou",
        children: [...]
    ]
}

深度/广度优先遍历

  • 深度优先遍历:尽可能深的搜索树的分支
  • 广度优先遍历:先访问离跟节点最近的节点

如下分别是两种方式 遍历的顺序。

广度和深度.png

深度优先遍历

const tree = {
    val: "a",
    children: [
        {
            val: "b",
            children: [
                { val: "d", children: [] },
                { val: "e", children: [] }
            ]
        },
        {
            val: "c",
            children: [
                { val: "f", children: [] },
                { val: "g", children: [] }
            ]
        }
    ]
}

const dfs = root => {
     console.log(root.val); // a,b,d,e,c,f,g
    root.children.forEach(dfs)
}

深度.png

广度优先遍历

const tree = {
    val: "a",
    children: [
        {
            val: "b",
            children: [
                { val: "d", children: [] },
                { val: "e", children: [] }
            ]
        },
        {
            val: "c",
            children: [
                { val: "f", children: [] },
                { val: "g", children: [] }
            ]
        }
    ]
}


通过队列的方式,将先遍历的放在队首,后面层级的放在队尾
const bfs = root => {
    const q = [root]
    
    while(q.length > 0) {
        const n = q.shift()
        console.log(n.val)
        n.children.forEach(child => {
            q.push(child)
        })
    }
}

广度.png

二叉树

什么是二叉树

  • 树中每个节点最多只能有两个子节点
  • 在js中通常用Object来模拟二叉树

先序遍历

  • 访问根节点
  • 对根节点的子树进行先序遍历
  • 对跟节点的子树进行先序遍历

先序遍历.png

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
        }
    }
}

// 递归版:
const preorder = root => {
    if (!root) return
    console.log(root.val) // 1245 367
    preorder(root.left)
    preorder(root.right)
}

// 非递归版:

const preorder = root => {
    if (!root) return
    const stack = [root]
    while(stack.length) {
        const n = stack.pop()
        console.log(n.val)
        if (n.right) stack.push(n.right)
        if (n.left) stack.push(n.left)
    }
    
}

中序遍历

  • 对根节点的左子树进行中序遍历。
  • 访问根节点
  • 对根节点的右子树进行中序遍历。

中序遍历.png

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
        }
    }
}

// 递归版:
const inorder = root => {
    if (!root) return
    inorder(root.left)
    console.log(root.val)
    inorder(root.right)
}

后序遍历

  • 对根节点的左子树进行中序遍历。
  • 对根节点的右子树进行中序遍历。
  • 访问根节点

后序遍历.png

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
        }
    }
}

const postorder = root => {
    if (!root) return
    postorder(root.left)
    postorder(root.right)
    console.log(root.val)
}

小试牛刀

LeetCode-104: 二叉树最大深度

**示例:**\
给定二叉树 `[3,9,20,null,null,15,7]`3
   / \
  9  20
    /  \
   15   7
   
 /**
 * 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 maxDepth = function(root) {
    let res = 0;
    const dfs = (n, l) => {
        if (!n) return;
        res = Math.max(res, l)
        dfs(n.left, l + 1);
        dfs(n.right, l + 1);
    }
    dfs(root, 1);
    return res
};

LeetCode-111: 二叉树最小深度

image.png



输入:root = [3,9,20,null,null,15,7]
输出:2
示例 2:

输入:root = [2,null,3,null,4,null,5,null,6]
输出:5

来源:力扣(LeetCode/**
 * 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 minDepth = function(root) {
   if (!root) return 0;
    let stack = [ [root, 1] ]
    while(stack.length) {
        const [n, l] = stack.shift();
        console.log(n.val, l)
        if (!n.left && !n.right) return l
        if (n.left) stack.push([n.left, l + 1])
        if (n.right) stack.push([n.right, l + 1])
    }
};

LeetCode-102: 二叉树的层序遍历

示例:
二叉树:[3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回其层序遍历结果:

[
  [3],
  [9,20],
  [15,7]
]

来源:力扣(LeetCode/**
 * 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 levelOrder = function(root) {
    if (!root) return []
    const q = [ [root, 0] ]
    const res = []
    while(q.length) {
        const [n, level] = q.shift()
        if (!res[level]) {
            res.push([n.val])
        } else {
            console.log(res, level)
            res[level].push(n.val)
        }
        if (n.left) q.push([n.left, level + 1])
        if (n.right) q.push([n.right, level + 1])
    }
    return res
};


LeetCode-102: 二叉树的层序遍历

image.png

输入:root = [1,null,2,3]
输出:[1,3,2]
示例 2:

输入:root = []
输出:[]
示例 3:

输入:root = [1]
输出:[1]

来源:力扣(LeetCode)

/**
 * 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) {
    const res = []
    const rec = n => {
        if (!root) return
        rec(n.left)
        res.push(n.val)
        rec(n.right)
    }
    rec(root)
    return res
};


LeetCode-112: 路径总和

image.png

输入: root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出: true

image.png

输入:root = [1,2,3], targetSum = 5
输出:false

示例 3:
输入:root = [1,2], targetSum = 0
输出:false

来源:力扣(LeetCode)


/**
 * 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
 * @param {number} targetSum
 * @return {boolean}
 */
var hasPathSum = function(root, targetSum) {
    if (!root) return false;
    let res = false
    const dfs = (n, s) => {
        if (!n.left && !n.right && s === targetSum) {
            res = true
        }
        if (n.left) dfs(n.left, s + n.left.val)
        if (n.right) dfs(n.right, s + n.right.val)
    }
    dfs(root, root.val)
    return res
};

前端与树

遍历JSON的所有节点值

const json = {
    a: { b: { c: 1 }  } ,
    d: [1, 2]
}

const dfs = (n, path) => {
    if (!n) return
    console.log(n, path)
    Object.keys(n).forEach(k => {
        dfs(n[k], path.concat(k))
    })
}

dfs(json, [])

总结:

  • 树 是一种分层的数据抽象模型
  • 树的常用操作:深度/广度优先遍历、先中后序遍历...