樹
- 一種分層數據的抽象模型
- 前端工作中常見的樹包括:DOM樹、級聯選擇器、樹形控件
- JS中沒有樹,但是可以用Object和Array構建樹
- 樹的常用操作:深度/廣度優先遍歷、先中后序遍歷
樹的深度與廣度優先遍歷
-
深度優先遍歷:盡可能深的搜索樹的分支
image.png -
廣度優先遍歷:先訪問離根節點最近的節點
image.png
深度優先遍歷
- 訪問根節點
- 對根節點的children挨個進行深度優先遍歷
- 遞歸
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)
廣度優先遍歷
- 新建一個隊列,把根節點入隊
- 把隊頭出隊并訪問
- 把隊頭的children挨個入隊
- 重復第二步、第三步直到隊列為空
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)
二叉樹
- 樹中每個節點最多只能有兩個子節點
- 在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
}
}
}
先序遍歷
- 訪問根節點
- 對根節點的左子樹進行先序遍歷
- 對根節點的右子樹進行先序遍歷
image.png
const preorder = (root) => {
if (!root) return
console.log(root.val)
preorder(root.left)
preorder(root.right)
}
preorder(bt)
中序遍歷
- 對根節點的左子樹進行中序遍歷
- 訪問根節點
- 對根節點的右子樹進行中序遍歷
image.png
const inorder = (root) => {
if (!root) return
inorder(root.left)
console.log(root.val)
inorder(root.right)
}
inorder(bt)
后序遍歷
- 對根節點的左子樹進行后序遍歷
- 對根節點的右子樹進行后序遍歷
- 訪問根節點
image.png
const postorder = (root) => {
if (!root) return
postorder(root.left)
postorder(root.right)
console.log(root.val)
}
postorder(bt)
二叉樹的先中后序遍歷(非遞歸)
二叉樹先序遍歷
// 遞歸版
const preorder = root => {
...
preorder(...)
...
}
如果在函數里面,調用另外一個函數,就會形成一個函數調用堆棧,調用完畢后又被釋放
所以說,堆棧就是我們實現非遞歸版先中后序遍歷的關鍵,我們可以用堆棧來模擬遞歸操作
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)
}
}
二叉樹的最大深度
- 求最大深度,考慮使用深度優先遍歷
- 在深度優先遍歷過程中,記錄每個節點所在層級,找出最大層級
// 104.二叉樹的最大深度
var maxDepth = function(root) {
let res = 0
const dfs = (n, l) => {
if (!n) return
if (!n.left && !n.right) {
// 葉子節點再計算最深層級
res = Math.max(res, l)
}
dfs(n.left, l + 1)
dfs(n.right, l + 1)
}
dfs(root, 1)
return res
};
二叉樹的最小深度
思路:
- 求最小深度,考慮使用廣度優先遍歷
- 在廣度優先遍歷過程中,遇到葉子節點停止遍歷,返回節點層級
解題步驟:
- 廣度優先遍歷整棵樹,并記錄每個節點的層級
- 遇到葉子節點,返回節點層級,停止遍歷
// 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])
}
};
二叉樹的層序遍歷
- 層序遍歷順序就是廣度優先遍歷
- 不過在遍歷時候需要記錄當前節點所處的層級,方便將其添加到不同的數組中
// 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
};
路徑總和
- 在深度優先遍歷的過程中,記錄當前路徑的節點值的和
- 在葉子節點處,判斷當前路徑的節點值的和是否等于目標值
// 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的所有節點值
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, [])
總結
- 樹是一種分層數據的抽象模型,在前端廣泛應用
- 樹的常用操作:深度/廣度優先遍歷、先中后序遍歷...