深度优先遍历(DFS)和广度优先遍历(BFS)的实现与简单应用
实现
DFS(Depth First Search) 深度优先遍历
深度优先遍历是指从某个顶点出发, 首先访问这个顶点, 然后找出刚访问这个节点的第一个未被访问的子节点,然后再以此子节点为顶点, 继续找它的下一个顶点进行访问。重复此步骤,直至所有节点都被访问完为止。
例如有这样一棵节点树:
const nodes = {
value: '1',
children: [
{
value: '2',
children: [{ value: '5', children: [{ value: '9' }] }]
},
{
value: '3',
children: [
{ value: '6', children: [{ value: '10' }] },
{ value: '7' }
]
},
{
value: '4',
children: [{ value: '8' }]
}
]
}
在深度优先的情况下应当输出:
['1', '2', '5', '9', '3', '6', '10', '7', '4', '8']
递归实现
对于这类结构化数据,使用递归是比较容易理解的,我们只需要把每次递归的返回值添加到结果数组即可
// *类型定义,之后的代码不再设置
type VNode<T> = {
value: T,
children?: VNode<T>[]
}
// *递归实现
function dfsRecursion<T>(node: VNode<T>) {
const values: T[] = [];
if (node) {
values.push(node.value);
node.children?.forEach(item => {
values.push(...dfsRecursion(item));
})
}
return values;
}
非递归实现
非递归实现需要引入栈结构
用于保存当前遍历的子树。由于栈是先进后出的结构,同时深度优先遍历是优先遍历每棵子树的第一棵子树,所以我们在压栈的的时候需要先将children
逆序。
function dfsNonRecursion<T>(node: VNode<T>) {
if (!node) return;
const values: T[] = [];
const stack = [node];
while (stack.length !== 0) {
const curNode = stack.pop();
if (!curNode) break;
values.push(curNode.value);
curNode.children && stack.push(...curNode.children.slice().reverse());
}
return values;
}
BFS(Breath First Search) 广度优先遍历
广度优先遍历是将树按照层级层层遍历,直到某一层全部遍历完毕再遍历下一层级,直到所有节点全部遍历完成。
在广度优先的情况下,前文的树应当输出:
[ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ]
非递归实现
广度优先遍历如果用递归来实现会比较麻烦,这里就展示非递归的实现。
深度优先遍历我们用的是栈
来实现,而广度优先遍历需要用到的是队列
。每当遍历到某棵子树,就将这颗子树的所有子树追加到队列中,以实现层层遍历。
function bfsNonRecursion<T>(node: VNode<T>) {
if (!node) return;
const values: T[] = [];
const queue = [node];
while (queue.length !== 0) {
const curNode = queue.shift();
if (!curNode) break;
values.push(curNode.value);
if (curNode.children) queue.push(...curNode.children);
}
return values;
}
用深度优先遍历思想实现深拷贝
我们的深拷贝接收一个item
,并根据item
的类型进行不同的处理,如果item
是数组,则使用map
方法生成新数组,并传入deepClone
进行递归调用;如果item
是对象,则遍历对象的每个成员,并对每个成员都递归调用deepClone
,将处理程序交由下一次函数调用来处理;如果item
是简单数据类型,则直接返回,不做处理。
function deepClone(item: any): any {
if (Array.isArray(item)) return item.map(deepClone);
if (Object.prototype.toString.call(item) === '[object Object]') {
const obj: any = {};
for (const key in item) {
obj[key] = deepClone(item[key]);
}
return obj;
}
return item;
}
当然,这只是简单的实现,对于对象的循环引用等问题还需要做进一步的处理。