Skip to content

树结构遍历

下面是 JavaScript 树菜单遍历的详细示例,包含深度优先遍历(DFS)广度优先遍历(BFS) 两种核心方式,并附具体应用场景说明。

1. 树菜单数据结构定义

先定义一个典型的多级菜单树结构(包含 idnamechildren 等核心字段):

javascript
// 树菜单数据(多级 menu tree)
const menuTree = [
  {
    id: "1",
    name: "首页",
    icon: "home",
    children: [], // 叶子节点(无子菜单)
  },
  {
    id: "2",
    name: "用户管理",
    icon: "user",
    children: [
      {
        id: "2-1",
        name: "用户列表",
        icon: "list",
        children: [
          { id: "2-1-1", name: "添加用户", icon: "add" },
          { id: "2-1-2", name: "编辑用户", icon: "edit" },
        ],
      },
      { id: "2-2", name: "角色权限", icon: "lock", children: [] },
    ],
  },
  {
    id: "3",
    name: "系统设置",
    icon: "setting",
    children: [{ id: "3-1", name: "基础配置", icon: "config", children: [] }],
  },
];

2. 深度优先遍历(DFS)

特点:从根节点出发,优先遍历完所有子节点(先深入,再回溯),适合层级嵌套展示(如递归渲染菜单组件)。

(1)递归实现(最常用)

javascript
/**
 * 深度优先遍历(递归版)
 * @param {Array} tree - 树菜单数组
 * @param {Function} callback - 节点处理函数(接收当前节点和层级)
 * @param {number} level - 当前层级(默认从 1 开始)
 */
function dfsRecursive(tree, callback, level = 1) {
  // 遍历当前层级的节点
  tree.forEach((node) => {
    // 处理当前节点(传入节点和层级)
    callback(node, level);

    // 若有子节点,递归遍历下一级(层级 +1)
    if (node.children && node.children.length > 0) {
      dfsRecursive(node.children, callback, level + 1);
    }
  });
}

// 使用示例:打印所有节点的名称和层级
console.log("DFS 递归遍历结果:");
dfsRecursive(menuTree, (node, level) => {
  // 用缩进表示层级(level 越大,缩进越多)
  const indent = "  ".repeat(level - 1);
  console.log(`${indent}${level}级 - ${node.name}(id: ${node.id})`);
});

输出结果(层级清晰,先深入到最子节点再回溯):

DFS 递归遍历结果:
1级 - 首页(id: 1)
1级 - 用户管理(id: 2)
  2级 - 用户列表(id: 2-1)
    3级 - 添加用户(id: 2-1-1)
    3级 - 编辑用户(id: 2-1-2)
  2级 - 角色权限(id: 2-2)
1级 - 系统设置(id: 3)
  2级 - 基础配置(id: 3-1)

(2)非递归实现(用栈模拟递归)

适合处理超大型树(避免递归栈溢出):

javascript
/**
 * 深度优先遍历(非递归版,用栈实现)
 * @param {Array} tree - 树菜单数组
 * @param {Function} callback - 节点处理函数
 */
function dfsStack(tree, callback) {
  // 初始化栈(存储 [节点, 层级])
  const stack = [];
  // 根节点入栈(层级为 1)
  tree.forEach((node) => stack.push([node, 1]));

  // 栈不为空则继续遍历
  while (stack.length > 0) {
    // 弹出栈顶节点(LIFO 后进先出)
    const [node, level] = stack.pop();

    // 处理当前节点
    callback(node, level);

    // 子节点入栈(注意:反序入栈,保证遍历顺序正确)
    if (node.children && node.children.length > 0) {
      // 从最后一个子节点开始入栈(因栈是后进先出)
      for (let i = node.children.length - 1; i >= 0; i--) {
        stack.push([node.children[i], level + 1]);
      }
    }
  }
}

// 使用示例:收集所有节点的 id
const allIds = [];
dfsStack(menuTree, (node) => {
  allIds.push(node.id);
});
console.log("DFS 非递归收集的 id:", allIds);
// 输出:['1', '2', '2-1', '2-1-1', '2-1-2', '2-2', '3', '3-1']

3. 广度优先遍历(BFS)

特点:按层级顺序遍历(先遍历完同一层级,再进入下一层),适合层级统计(如计算菜单总层数、查找某层级的所有节点)。

实现(用队列)

javascript
/**
 * 广度优先遍历(用队列实现)
 * @param {Array} tree - 树菜单数组
 * @param {Function} callback - 节点处理函数(接收当前节点和层级)
 */
function bfs(tree, callback) {
  // 初始化队列(存储 [节点, 层级])
  const queue = [];
  // 根节点入队(层级为 1)
  tree.forEach((node) => queue.push([node, 1]));

  // 队列不为空则继续遍历
  while (queue.length > 0) {
    // 出队首节点(FIFO 先进先出)
    const [node, level] = queue.shift();

    // 处理当前节点
    callback(node, level);

    // 子节点入队(按顺序入队,保证层级顺序)
    if (node.children && node.children.length > 0) {
      node.children.forEach((child) => {
        queue.push([child, level + 1]);
      });
    }
  }
}

// 使用示例:按层级分组统计节点
const levelGroups = {};
bfs(menuTree, (node, level) => {
  // 按层级分组
  if (!levelGroups[level]) {
    levelGroups[level] = [];
  }
  levelGroups[level].push(node.name);
});
console.log("BFS 层级分组结果:", levelGroups);

输出结果(按层级分组,先处理完上层再处理下层):

BFS 层级分组结果:
{
  '1': ['首页', '用户管理', '系统设置'],
  '2': ['用户列表', '角色权限', '基础配置'],
  '3': ['添加用户', '编辑用户']
}

4. 实际应用场景

(1)渲染多级菜单(DFS 递归渲染)

在前端框架(如 Vue/React)中,用 DFS 递归渲染嵌套菜单:

javascript
// 模拟 React 组件递归渲染菜单
function renderMenu(tree) {
  return tree.map((node) => (
    <div key={node.id} className={`menu-item level-${level}`}>
      <span>{node.name}</span>
      {/* 递归渲染子菜单 */}
      {node.children && node.children.length > 0 && renderMenu(node.children)}
    </div>
  ));
}

(2)查找最深层级的节点(BFS 应用)

javascript
// 查找菜单树的最大深度(最深层级)
function getMaxDepth(tree) {
  let maxDepth = 0;
  bfs(tree, (node, level) => {
    if (level > maxDepth) {
      maxDepth = level;
    }
  });
  return maxDepth;
}
console.log("菜单最大深度:", getMaxDepth(menuTree)); // 输出:3

(3)筛选包含特定关键词的菜单(结合 DFS)

javascript
// 查找名称包含「用户」的所有菜单节点
function findMenusByKeyword(tree, keyword) {
  const result = [];
  dfsRecursive(tree, (node) => {
    if (node.name.includes(keyword)) {
      result.push(node);
    }
  });
  return result;
}
const userMenus = findMenusByKeyword(menuTree, "用户");
console.log(
  "包含「用户」的菜单:",
  userMenus.map((m) => m.name)
);
// 输出:['用户管理', '用户列表', '添加用户', '编辑用户', '角色权限']

总结

遍历方式核心思想适用场景实现方式
DFS先深入子节点再回溯递归渲染菜单、查找路径、深度相关操作递归/栈
BFS按层级顺序遍历层级统计、广度相关操作、最短路径队列

实际开发中,根据需求选择遍历方式:展示嵌套结构用 DFS,层级统计用 BFS;小型树用递归更简洁,大型树用非递归(栈/队列)更安全。