開發中常常要對數據作一些處理,大多狀況下數據是固定層級和結構的,但也有一些狀況下數據的層級和結構是不固定的,好比文件目錄、功能菜單、權限樹等,這種結構的數據的處理須要涉及到樹的遍歷算法。node
const data = {
name: 'all',
children: [
{
name: '圖片',
children: [
{
name: 'image1.jpg'
},
{
name: '風景',
children: [
{
name: 'guilin.jpg'
},
{
name: 'hainan.jpg'
}
]
},
{
name: 'image2.jpg'
}
],
},
{
name: '視頻',
children: [
{
name: 'video1.mp4'
},
{
name: 'video2.mp4'
}
]
},
{
name: '文檔',
children: [
{
name: 'document1.doc'
},
{
name: '小說',
children: [
{
name: 'novel.txt'
},
{
name: 'novel2.txt'
}
]
},
{
name: 'document2.doc'
}
]
}
]
}
複製代碼
樹的遍歷有深度優先和廣度優先兩種方式。深度優先遍歷的形式是遞歸,優勢是代碼簡潔直觀,缺點是層級過深的時候可能會棧溢出,只適用於層級較少的狀況,廣度優先遍歷的優勢是不會棧溢出,適應任意層級深度,但缺點是須要引入一個隊列來存儲待遍歷的節點,空間複雜度較高。git
const dfs = (tree, ope) => {
const walk = (tree, depth = 1) => {
ope(tree.name, depth)
if(tree.children) {
tree.children.forEach((node) => {
walk(node, depth + 1)
})
}
}
walk(tree)
}
複製代碼
測試:github
dfs(data, (name, depth) => {
let pre = '';
for(let i =0; i < depth; i++) {
pre += '--'
}
console.log(pre + name)
})
複製代碼
點我去運行算法
const bfs = (tree, ope) => {
const walk = (tree, depth = 1) => {
const queue = []
ope(tree.name, depth)
if(tree.children){
queue.push({
nodes: tree.children,
depth: depth + 1
})
}
while(queue.length) {
const item = queue.pop()
item.nodes && item.nodes.forEach(node => {
ope(node.name, item.depth)
if(node.children) {
queue.push({
nodes: node.children,
depth: item.depth + 1
})
}
})
}
}
walk(tree)
}
複製代碼
測試:bash
bfs(data,(name, depth) => {
let pre = '';
for(let i =0; i < depth; i++) {
pre += '--'
}
console.log(pre + name)
})
複製代碼
點我去運行ide
不少狀況下,咱們不僅須要遍歷這棵樹,可能還須要對這棵樹進行一些過濾,返回過濾之後的數據,好比權限樹的過濾、文件目錄結構的過濾、功能菜單的過濾。大多數狀況下過濾後的數據依然要保留樹型結構。測試
其實,對樹形結構的各類操做都是創建在遍歷的基礎之上,實現過濾的功能只須要在遍歷的時候加一個判斷,而且把符合條件的節點按照層級關係複製一份。ui
代碼以下:spa
const dfs = (tree, ope, filter) => {
const walkAndCopy = (tree, depth = 1) => {
if(filter(tree.name)) {
const copy = {}
ope(tree.name, depth)
copy.name = tree.name
if(tree.children) {
copy.children = []
tree.children.forEach((node) => {
const subTree = walkAndCopy(node, depth + 1)
subTree && copy.children.push(subTree)
})
}
return copy
}
}
return walkAndCopy(tree)
}
複製代碼
測試代碼(過濾掉全部名字中含有1的文件和目錄):.net
const copy = dfs(data,(name, depth) => {}, (name) => {
return name.indexOf('1') === -1
})
console.log(copy)
複製代碼
const bfs = (tree, ope, filter) => {
const walkAndCopy = (tree, depth = 1) => {
const queue = []
if (filter(tree.name)) {
const copy = {}
ope(tree.name, depth)
copy.name = tree.name
if(tree.children){
copy.children = []
queue.push({
nodes: tree.children,
depth: depth + 1,
copyNodes: copy.children
})
}
while(queue.length) {
const item = queue.pop()
item.nodes && item.nodes.forEach(node => {
if(filter(node.name)) {
const copyNode = {}
ope(node.name, item.depth)
copyNode.name = node.name
if(node.children) {
copyNode.children = []
queue.push({
nodes: node.children,
depth: item.depth + 1,
copyNodes: copyNode.children
})
}
item.copyNodes.push(copyNode)
}
})
}
return copy
}
}
return walkAndCopy(tree)
}
複製代碼
測試代碼(過濾掉全部名字中含有1的文件和目錄):
const copy = bfs(data,(name, depth) => {}, (name) => {
return name.indexOf('1') === -1
})
console.log(copy)
複製代碼
開發中偶爾會有一些層級和結構不固定的樹型數據,須要對這些數據進行處理,對樹型數據的處理創建在遍歷的基礎之上,遍歷分爲深度優先和廣度優先兩種,深度優先基於遞歸,代碼直觀但可能爆棧,只適用於層級較少的狀況,廣度優先須要結合一個隊列,適應任意層級,但空間複雜度略高。對樹型數據的過濾只須要在遍歷的時候複製過濾後的數據,按照原有結構組合便可。