樹
- 一種分層數(shù)據(jù)的抽象模型
- 前端工作中常見的樹包括:DOM樹砸民、級聯(lián)選擇器捉撮、樹形控件
- JS中沒有樹蔓彩,但是可以用Object和Array構(gòu)建樹
- 樹的常用操作:深度/廣度優(yōu)先遍歷恳守、先中后序遍歷
樹的深度與廣度優(yōu)先遍歷
-
深度優(yōu)先遍歷:盡可能深的搜索樹的分支
-
廣度優(yōu)先遍歷:先訪問離根節(jié)點最近的節(jié)點
深度優(yōu)先遍歷
- 訪問根節(jié)點
- 對根節(jié)點的children挨個進(jìn)行深度優(yōu)先遍歷
- 遞歸
const tree = {
val: 'a',
children: [
{
val: 'b',
children: [
{
val: 'd',
children: []
},
{
val: 'e',
children: []
}
]
},
{
val: 'c',
children: [
{
val: 'f',
children: []
}
]
}
]
}
const dfs = (root) => {
console.log(root)
root.children.forEach(dfs)
}
dfs(tree)
廣度優(yōu)先遍歷
- 新建一個隊列,把根節(jié)點入隊
- 把隊頭出隊并訪問
- 把隊頭的children挨個入隊
- 重復(fù)第二步砌们、第三步直到隊列為空
const tree = {
val: 'a',
children: [
{
val: 'b',
children: [
{
val: 'd',
children: []
},
{
val: 'e',
children: []
}
]
},
{
val: 'c',
children: [
{
val: 'f',
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)
})
}
}
bfs(tree)
二叉樹
- 樹中每個節(jié)點最多只能有兩個子節(jié)點
- 在JS中通常用Object來模擬二叉樹
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
}
}
}
先序遍歷
- 訪問根節(jié)點
- 對根節(jié)點的左子樹進(jìn)行先序遍歷
- 對根節(jié)點的右子樹進(jìn)行先序遍歷
const preorder = (root) => {
if (!root) return
console.log(root.val)
preorder(root.left)
preorder(root.right)
}
preorder(bt)
中序遍歷
- 對根節(jié)點的左子樹進(jìn)行中序遍歷
- 訪問根節(jié)點
- 對根節(jié)點的右子樹進(jìn)行中序遍歷
const inorder = (root) => {
if (!root) return
inorder(root.left)
console.log(root.val)
inorder(root.right)
}
inorder(bt)
后序遍歷
- 對根節(jié)點的左子樹進(jìn)行后序遍歷
- 對根節(jié)點的右子樹進(jìn)行后序遍歷
- 訪問根節(jié)點
const postorder = (root) => {
if (!root) return
postorder(root.left)
postorder(root.right)
console.log(root.val)
}
postorder(bt)
二叉樹的先中后序遍歷(非遞歸)
二叉樹先序遍歷
// 遞歸版
const preorder = root => {
...
preorder(...)
...
}
如果在函數(shù)里面顷歌,調(diào)用另外一個函數(shù)蚜退,就會形成一個函數(shù)調(diào)用堆棧滩租,調(diào)用完畢后又被釋放
所以說颗圣,堆棧就是我們實現(xiàn)非遞歸版先中后序遍歷的關(guān)鍵,我們可以用堆棧來模擬遞歸操作
const preorder = root => {
if (!root) return
const stack = [root]
while (stack.length) {
const n = stack.pop()
console.log(n)
if (n.right) stack.push(n.right)
if (n.left) stack.push(n.left)
}
}
二叉樹中序遍歷
const inorder = root => {
if (!root) return
const stack = []
let 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
}
}
二叉樹后序遍歷
const postorder = 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) {
const n = outputStack.pop()
console.log(n.val)
}
}
二叉樹的最大深度
- 求最大深度篮撑,考慮使用深度優(yōu)先遍歷
- 在深度優(yōu)先遍歷過程中减细,記錄每個節(jié)點所在層級,找出最大層級
// 104.二叉樹的最大深度
var maxDepth = function(root) {
let res = 0
const dfs = (n, l) => {
if (!n) return
if (!n.left && !n.right) {
// 葉子節(jié)點再計算最深層級
res = Math.max(res, l)
}
dfs(n.left, l + 1)
dfs(n.right, l + 1)
}
dfs(root, 1)
return res
};
二叉樹的最小深度
思路:
- 求最小深度赢笨,考慮使用廣度優(yōu)先遍歷
- 在廣度優(yōu)先遍歷過程中未蝌,遇到葉子節(jié)點停止遍歷,返回節(jié)點層級
解題步驟:
- 廣度優(yōu)先遍歷整棵樹茧妒,并記錄每個節(jié)點的層級
- 遇到葉子節(jié)點萧吠,返回節(jié)點層級,停止遍歷
// 111.二叉樹的最小深度
var minDepth = function(root) {
if (!root) {return 0}
const q = [[root, 1]]
while (q.length > 0) {
const [n, l] = q.shift()
if (!n.left && !n.right) return l
if (n.left) q.push([n.left, l+1])
if (n.right) q.push([n.right, l + 1])
}
};
二叉樹的層序遍歷
- 層序遍歷順序就是廣度優(yōu)先遍歷
- 不過在遍歷時候需要記錄當(dāng)前節(jié)點所處的層級桐筏,方便將其添加到不同的數(shù)組中
// 102.二叉樹的層序遍歷
var levelOrder = function(root) {
if (!root) return []
const q = [root]
const res = []
while (q.length) {
let len = q.length
res.push([])
while (len--) {
const n = q.shift()
res[res.length - 1].push(n.val)
if (n.left) q.push(n.left)
if (n.right) q.push(n.right)
}
}
return res
};
二叉樹的中序遍歷
// 94.二叉樹的中序遍歷
// 遞歸
var inorderTraversal = function(root) {
const res = []
const rec = n => {
if (!n) return
rec(n.left)
res.push(n.val)
rec(n.right)
}
rec(root)
return res
};
// 非遞歸
var inorderTraversal = function(root) {
const res = []
const stack = []
let p = root
while (stack.length || p) {
while(p) {
stack.push(p)
p = p.left
}
const n = stack.pop()
res.push(n.val)
p = n.right
}
return res
};
路徑總和
- 在深度優(yōu)先遍歷的過程中纸型,記錄當(dāng)前路徑的節(jié)點值的和
- 在葉子節(jié)點處,判斷當(dāng)前路徑的節(jié)點值的和是否等于目標(biāo)值
// 112.路徑總和
var hasPathSum = function(root, targetSum) {
if (!root) return false
let result = false
const dfs = (n, val) => {
if (!n.left && !n.right && val === targetSum) result = true
if (n.left) dfs(n.left, n.left.val + val)
if (n.right) dfs(n.right, n.right.val + val)
}
dfs(root, root.val)
return result
};
遍歷JSON的所有節(jié)點值
const json = {
a: {
b: {
c: 1
}
},
d: [1, 2]
}
dfs = (n, path) => {
console.log(n)
Object.keys(n).forEach(k => {
dfs(n[k], path.concat(k))
})
}
dfs(json, [])
總結(jié)
- 樹是一種分層數(shù)據(jù)的抽象模型梅忌,在前端廣泛應(yīng)用
- 樹的常用操作:深度/廣度優(yōu)先遍歷狰腌、先中后序遍歷...