電商分類、CMS 欄目、組織架構等層級數據常以「扁平數組 + pid 指向父級」形式存儲。構建樹形結構的最優解是「單次遍歷 + 內存引用」,無需遞歸、無需多次查庫,時間複雜度 O(n)。
$items[3]['id'] === 3)id、pid(父級 ID,頂級為 0)$items = [
1 => ['id'=>1, 'pid'=>0, 'name'=>'江西省'],
2 => ['id'=>2, 'pid'=>0, 'name'=>'黑龍江省'],
3 => ['id'=>3, 'pid'=>1, 'name'=>'南昌市'],
4 => ['id'=>4, 'pid'=>2, 'name'=>'哈爾濱市'],
5 => ['id'=>5, 'pid'=>2, 'name'=>'雞西市'],
6 => ['id'=>6, 'pid'=>4, 'name'=>'香坊區'],
7 => ['id'=>7, 'pid'=>4, 'name'=>'南崗區'],
8 => ['id'=>8, 'pid'=>6, 'name'=>'和興路'],
9 => ['id'=>9, 'pid'=>7, 'name'=>'西大直街'],
10 => ['id'=>10, 'pid'=>8, 'name'=>'東北林業大學'],
11 => ['id'=>11, 'pid'=>9, 'name'=>'哈爾濱工業大學'],
12 => ['id'=>12, 'pid'=>8, 'name'=>'哈爾濱師范大學'],
13 => ['id'=>13, 'pid'=>1, 'name'=>'贛州市'],
14 => ['id'=>14, 'pid'=>13, 'name'=>'贛縣'],
15 => ['id'=>15, 'pid'=>13, 'name'=>'於都縣'],
16 => ['id'=>16, 'pid'=>14, 'name'=>'茅店鎮'],
17 => ['id'=>17, 'pid'=>14, 'name'=>'大田鄉'],
18 => ['id'=>18, 'pid'=>16, 'name'=>'義源村'],
19 => ['id'=>19, 'pid'=>16, 'name'=>'上壩村'],
];
function genTree5($items) {
foreach ($items as $item)
$items[$item['pid']]['son'][$item['id']] = &$items[$item['id']];
return isset($items[0]['son']) ? $items[0]['son'] : [];
}
function genTree9($items) {
$tree = [];
foreach ($items as $item) {
if (isset($items[$item['pid']])) {
// 父級存在,掛載到父級的 son 數組
$items[$item['pid']]['son'][] = &$items[$item['id']];
} else {
// 父級不存在(pid=0 或數據不全),作為根節點
$tree[] = &$items[$item['id']];
}
}
return $tree;
}
Array
(
[0] => Array
(
[id] => 1
[pid] => 0
[name] => 江西省
[son] => Array
(
[0] => Array ([id]=>3, [pid]=>1, [name]=>南昌市)
[1] => Array
(
[id] => 13
[pid] => 1
[name] => 贛州市
[son] => Array
(
[0] => Array
(
[id] => 14
[pid] => 13
[name] => 贛縣
[son] => Array
(
[0] => Array
(
[id] => 16
[pid] => 14
[name] => 茅店鎮
[son] => Array
(
[0] => Array ([id]=>18, [pid]=>16, [name]=>義源村)
[1] => Array ([id]=>19, [pid]=>16, [name]=>上壩村)
)
)
[1] => Array ([id]=>17, [pid]=>14, [name]=>大田鄉)
)
)
[1] => Array ([id]=>15, [pid]=>13, [name]=>於都縣)
)
)
)
)
[1] => Array
(
[id] => 2
[pid] => 0
[name] => 黑龍江省
[son] => Array
(
[0] => Array
(
[id] => 4
[pid] => 2
[name] => 哈爾濱市
[son] => Array
(
[0] => Array
(
[id] => 6
[pid] => 4
[name] => 香坊區
[son] => Array
(
[0] => Array
(
[id] => 8
[pid] => 6
[name] => 和興路
[son] => Array
(
[0] => Array ([id]=>10, [pid]=>8, [name]=>東北林業大學)
[1] => Array ([id]=>12, [pid]=>8, [name]=>哈爾濱師范大學)
)
)
)
)
[1] => Array
(
[id] => 7
[pid] => 4
[name] => 南崗區
[son] => Array
(
[0] => Array
(
[id] => 9
[pid] => 7
[name] => 西大直街
[son] => Array
(
[0] => Array ([id]=>11, [pid]=>9, [name]=>哈爾濱工業大學)
)
)
)
)
)
)
[1] => Array ([id]=>5, [pid]=>2, [name]=>雞西市)
)
)
)
| 技巧 | 作用 |
|---|---|
&$items[$item['id']] | 引用賦值,父子節點指向同一內存,修改任意一處即生效 |
$items[$item['pid']] | 利用一維鍵 = ID 特性,O(1) 直接定位父節點 |
| 單次 foreach | 無遞歸、無二次查詢,時間 O(n),空間 O(n) |
isset($items[$item['pid']]) | 兼容 pid=0(頂級)與髒數據(父級缺失) |
$map = array_column($items, null, 'id') 重建索引genTree($items, 'id', 'pid', 'children')uasort($items, fn(a,b)=>a['sort']-b['sort'])function buildTreeRecursive($items, $pid = 0) {
$tree = [];
foreach ($items as $item) {
if ($item['pid'] == $pid) {
$item['son'] = buildTreeRecursive($items, $item['id']);
$tree[] = $item;
}
}
return $tree;
}
// 缺點:O(n²) 時間、深層棧溢出風險、每層遍歷全量數據
「單次遍歷 + 內存引用」是 PHP 構建樹形結構的標準最優解,掌握後可應用於所有「扁平轉樹」場景。