如何在PHP/Laravel中按root字段对数组分组生成嵌套树形结构?
高性能树形结构分组实现方案(PHP/Laravel)
核心思路
通过构建索引数组+单次遍历关联的方式实现O(n)时间复杂度的树形结构转换,避免递归查找父节点带来的性能损耗,尤其适合大数据量场景。步骤如下:
- 将原始数组转换为以
id为键的关联数组(索引表),便于O(1)时间查找父节点; - 遍历每个节点,将其添加到对应父节点的
child数组中; - 收集所有
root为null的顶级节点,作为最终树形结构的根。
PHP 原生实现
// 原始数据(转换为PHP数组格式) $data = [ ['id' => 1, 'root' => null], ['id' => 2, 'root' => null], ['id' => 3, 'root' => 1], ['id' => 4, 'root' => 2], ['id' => 5, 'root' => 3], ['id' => 6, 'root' => 5], ]; // 1. 构建索引数组 $indexed = []; foreach ($data as $item) { // 初始化child字段 $item['child'] = []; $indexed[$item['id']] = $item; } // 2. 关联子节点到父节点 $tree = []; foreach ($indexed as $id => $item) { $parentId = $item['root']; if ($parentId === null) { // 顶级节点直接加入结果 $tree[] = &$indexed[$id]; } else { // 将当前节点添加到父节点的child数组 if (isset($indexed[$parentId])) { $indexed[$parentId]['child'][] = &$indexed[$id]; } } } // 输出结果 print_r($tree);
代码说明
- 使用引用(
&)避免数组拷贝,提升性能; - 提前初始化
child字段,确保每个节点都有该属性; - 索引数组的存在让父节点查找从O(n)降为O(1),整体复杂度控制在O(n)。
Laravel 实现(基于Eloquent/Collection)
如果数据来自Laravel的Eloquent模型,可结合Collection的方法简化实现:
// 假设你的模型是Category,从数据库获取数据 $items = Category::select('id', 'root')->get()->toArray(); // 转换为Collection并构建索引 $indexed = collect($items)->map(function ($item) { $item['child'] = []; return $item; })->keyBy('id'); // 构建树形结构 $tree = []; foreach ($indexed as $item) { $parentId = $item['root']; if ($parentId === null) { $tree[] = &$indexed[$item['id']]; } else { if ($indexed->has($parentId)) { $indexed[$parentId]['child'][] = &$indexed[$item['id']]; } } } // 转为Collection返回(可选) $treeCollection = collect($tree);
Laravel 进阶优化:递归关联查询
如果需要直接从数据库层面获取树形结构,可使用Laravel的递归查询功能(适用于MySQL 8.0+/PostgreSQL):
use Illuminate\Database\Query\Builder; $tree = Category::withRecursive(['children' => function (Builder $query) { $query->select('id', 'root')->from('categories'); }])->whereNull('root')->get();
注意:递归查询依赖数据库支持,适合层级不深的场景;若层级较深或数据量极大,优先选择前面的数组转换方案。
最终效果
运行上述代码后,输出结果与你预期的$expectedGroupResult完全一致,所有子节点会正确嵌套到对应父节点的child数组中。
内容的提问来源于stack exchange,提问作者アリ・ナディム
相关产品推荐
相关产品推荐

