问题描述
给定一个DOM节点的根元素,请分别实现它的深度优先遍历(DFS)和广度优先遍历(BFS),并返回所有节点名称的数组。
前端意义:这是理解虚拟DOM实现、DOM Diff算法以及任何需要递归操作DOM结构(如UI框架、爬虫、自动化测试)的基础。
解题思路
-
深度优先遍历 (DFS) :
-
从根节点开始,尽可能深地访问每个分支。
-
通常采用递归 或栈(Stack) 的思想实现。
-
顺序:A -> B -> D -> E -> C -> F
text
复制下载
cssA / \ B C / \ \ D E F
-
-
广度优先遍历 (BFS) :
- 从根节点开始,一层一层地访问。
- 通常采用队列(Queue) 的思想实现。
- 顺序:A -> B -> C -> D -> E -> F
代码实现
scss
// 假设我们有一个DOM节点,它有一个`children`属性,包含其所有子元素。
/**
* 深度优先遍历 - 递归版 (最简洁)
*/
function dfsTraversalRecursive(node) {
const result = [];
function traverse(node) {
if (!node) return;
result.push(node.nodeName); // 访问节点
// 遍历所有子节点
for (const child of node.children) {
traverse(child); // 递归遍历子树
}
}
traverse(node);
return result;
}
/**
* 深度优先遍历 - 迭代版 (利用栈)
* 更接近计算机底层实现,避免递归栈溢出风险
*/
function dfsTraversalIterative(root) {
const result = [];
const stack = [root]; // 初始化栈,放入根节点
while (stack.length > 0) {
const currentNode = stack.pop(); // 弹出栈顶节点
result.push(currentNode.nodeName); // 访问它
// 注意:由于栈是"后进先出",我们需要将子节点**逆序**入栈
// 这样才能保证下一个要处理的是第一个子节点,而不是最后一个
for (let i = currentNode.children.length - 1; i >= 0; i--) {
stack.push(currentNode.children[i]);
}
}
return result;
}
/**
* 广度优先遍历 - 迭代版 (利用队列)
*/
function bfsTraversal(root) {
const result = [];
const queue = [root]; // 初始化队列,放入根节点
while (queue.length > 0) {
const currentNode = queue.shift(); // 从队列头部取出节点
result.push(currentNode.nodeName); // 访问它
// 将当前节点的所有子节点**按顺序**加入队列尾部
for (const child of currentNode.children) {
queue.push(child);
}
}
return result;
}
// 示例用法
// const rootElement = document.getElementById('root');
// console.log('DFS Recursive:', dfsTraversalRecursive(rootElement));
// console.log('DFS Iterative:', dfsTraversalIterative(rootElement));
// console.log('BFS:', bfsTraversal(rootElement));
复杂度分析
- 时间复杂度:O(n)。所有算法都恰好访问每个节点一次。
- 空间复杂度:O(n)。在最坏情况下(树退化为链表),递归DFS和栈迭代DFS的空间复杂度为O(n)。BFS的空间复杂度取决于每一层的宽度,在最坏情况下(平衡二叉树)也是O(n)。