任意層級樹型數據的遍歷和過濾

樹型結構數據

開發中常常要對數據作一些處理,大多狀況下數據是固定層級和結構的,但也有一些狀況下數據的層級和結構是不固定的,好比文件目錄、功能菜單、權限樹等,這種結構的數據的處理須要涉及到樹的遍歷算法。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

深度優先(dfs)

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)
})
複製代碼

點我去運行算法

廣度優先(bfs)

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

dfs-filter

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)
複製代碼

點我去運行

bfs-filter

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)
複製代碼

點我去運行

總結

開發中偶爾會有一些層級和結構不固定的樹型數據,須要對這些數據進行處理,對樹型數據的處理創建在遍歷的基礎之上,遍歷分爲深度優先和廣度優先兩種,深度優先基於遞歸,代碼直觀但可能爆棧,只適用於層級較少的狀況,廣度優先須要結合一個隊列,適應任意層級,但空間複雜度略高。對樹型數據的過濾只須要在遍歷的時候複製過濾後的數據,按照原有結構組合便可。

github

相關文章
相關標籤/搜索