菜单树在软件开发中经常用于展示层级结构的数据,例如网站导航菜单或文件系统。在实际开发中,我们常常需要考虑不同的数据结构和算法来实现这种层级关系的展示。本文将介绍菜单树的三种常见实现方式:递归、for循环和队列,帮助读者更好地理解和选择合适的方法。
准备
示例数据库
sql
create table menu
(
id int auto_increment
primary key,
name varchar(100) not null,
parent_id int null
);
create index parent_id
on menu (parent_id);
Mapper
java
@Mapper
public interface MenuMapper extends BaseMapper<Menu> {
@Select("SELECT id, name, parent_id FROM menu WHERE parent_id = #{parentId}")
List<Menu> findMenusByParentId(@Param("parentId") Integer parentId);
}
Service
java
public interface MenuService {
List<Menu> findMenusByParentId(Integer parentId);
}
Impl
java
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
@Autowired
private MenuMapper menuMapper;
@Override
public List<Menu> findMenusByParentId(Integer parentId) {
return menuMapper.findMenusByParentId(parentId);
}
}
1. 递归实现
java
@RestController
@RequestMapping("/menus")
public class MenuController {
@Autowired
private MenuService menuService;
// 返回根菜单列表及其子菜单
@GetMapping("/tree")
public List<Menu> getMenuTree(Integer parentId) {
List<Menu> rootMenus = menuService.findMenusByParentId(parentId); // 获取顶级菜单
buildMenuTree(rootMenus); // 构建树形结构
return rootMenus;
}
// 递归方法,构建菜单树
private void buildMenuTree(List<Menu> menus) {
for (Menu menu : menus) {
List<Menu> children = menuService.findMenusByParentId(menu.getId());
if (!children.isEmpty()) {
menu.setChildren(children); // 设置子菜单列表
buildMenuTree(children); // 递归构建子树
}
}
}
}
2.For实现
java
@RestController
@RequestMapping("/treemenus")
public class MenuNotRecursionUseForController {
@Resource
private MenuService menuService;
@GetMapping("/tree")
public List<Menu> getMenuTree(Integer parentId) {
List<Menu> rootMenus = menuService.findMenusByParentId(parentId); // 获取顶级菜单列表
// 创建一个 Map 用于存放每个菜单的 ID 和对应的菜单对象
Map<Integer, Menu> menuMap = new HashMap<>();
for (Menu menu : rootMenus) {
menu.setChildren(new ArrayList<>()); // 初始化子菜单列表
menuMap.put(menu.getId(), menu); // 将菜单对象放入 map 中,以便后续快速访问
}
// 遍历第二层菜单,逐层构建树形结构
for (Menu menu : rootMenus) {
// 查询当前菜单的所有子菜单(第二层菜单)
List<Menu> children = menuService.findMenusByParentId(menu.getId());
for (Menu child : children) {
// 将第二层菜单添加到父菜单的子菜单列表中
Menu parentMenu = menuMap.get(child.getParentId());
if (parentMenu != null) {
parentMenu.getChildren().add(child);
// 查询当前第二层菜单的所有子菜单(第三层菜单)
List<Menu> grandchildren = menuService.findMenusByParentId(child.getId());
if (child.getChildren() == null) {
child.setChildren(new ArrayList<>()); // 初始化第三层子菜单列表
}
for (Menu grandchild : grandchildren) {
// 将第三层菜单添加到当前第二层菜单的子菜单列表中
child.getChildren().add(grandchild);
}
}
}
}
// 返回构建好的根菜单列表
return rootMenus;
}
}
3. 队列实现
java
@RestController
@RequestMapping("/treemenustwo")
public class MenuNotRecursionUseQueueController {
@Autowired
private MenuService menuService;
@GetMapping("/tree")
public List<Menu> getMenuTree(Integer parentId) {
List<Menu> rootMenus = menuService.findMenusByParentId(parentId); // 获取顶级菜单列表
// 使用队列来进行广度优先搜索
Queue<Menu> queue = new LinkedList<>(rootMenus);
while (!queue.isEmpty()) {
Menu currentMenu = queue.poll(); // 出队当前菜单
// 查询当前菜单的所有子菜单
List<Menu> children = menuService.findMenusByParentId(currentMenu.getId());
// 将子菜单加入当前菜单的 children 列表中
currentMenu.setChildren(children);
// 将子菜单加入队列,以便后续处理其子菜单
queue.addAll(children);
}
return rootMenus; // 返回构建好的树形结构
}
}