$areas = [
['id'=>1, 'name'=>'安徽', 'parent'=>0],
['id'=>2, 'name'=>'海澱', 'parent'=>7],
['id'=>3, 'name'=>'濉溪縣', 'parent'=>5],
['id'=>4, 'name'=>'昌平', 'parent'=>7],
['id'=>5, 'name'=>'淮北', 'parent'=>1],
['id'=>6, 'name'=>'朝陽', 'parent'=>7],
['id'=>7, 'name'=>'北京', 'parent'=>0],
['id'=>8, 'name'=>'上地', 'parent'=>2],
];
特點:扁平陣列、parent 指向父節點 id、根節點 parent=0。
<?php
/**
* 遞歸查找所有子孫節點
* @param array $data 扁平資料
* @param int $parent_id 父節點 ID
* @param int $level 當前層級 (從 1 開始)
* @return array 樹狀結構,含 level 欄位
*/
function build_subtree($data, $parent_id = 0, $level = 1) {
$tree = [];
foreach ($data as $node) {
if ($node['parent'] == $parent_id) {
$node['level'] = $level;
$children = build_subtree($data, $node['id'], $level + 1);
if ($children) {
$node['children'] = $children;
}
$tree[] = $node;
}
}
return $tree;
}
$tree = build_subtree($areas);
// 輸出展示
function print_tree($tree, $indent = '') {
foreach ($tree as $node) {
echo $indent . $node['name'] . " (ID:{$node['id']}, Level:{$node['level']})<br>";
if (!empty($node['children'])) {
print_tree($node['children'], $indent . ' ');
}
}
}
print_tree($tree);
/* 輸出:
安徽 (ID:1, Level:1)
淮北 (ID:5, Level:2)
濉溪縣 (ID:3, Level:3)
北京 (ID:7, Level:1)
海澱 (ID:2, Level:2)
上地 (ID:8, Level:3)
昌平 (ID:4, Level:2)
朝陽 (ID:6, Level:2)
*/
?>
<?php
/**
* 遞歸查找從根到指定節點的路徑
* @return array 由根至目標的節點陣列
*/
function build_ancestors_recursive($data, $target_id) {
$path = [];
foreach ($data as $node) {
if ($node['id'] == $target_id) {
if ($node['parent'] > 0) {
$path = array_merge($path, build_ancestors_recursive($data, $node['parent']));
}
$path[] = $node;
break;
}
}
return $path;
}
$breadcrumb = build_ancestors_recursive($areas, 8); // 上地
// 結果: 北京 → 海澱 → 上地
foreach ($breadcrumb as $node) {
echo "> {$node['name']} ";
}
?>
<?php
/**
* 迭代查找祖先路徑 (無遞歸呼叫開銷)
*/
function build_ancestors_iterative($data, $target_id) {
$path = [];
$current_id = $target_id;
// 建立 ID -> 節點映射,O(1) 查找
$map = [];
foreach ($data as $node) {
$map[$node['id']] = $node;
}
while ($current_id !== 0 && isset($map[$current_id])) {
$node = $map[$current_id];
$path[] = $node;
$current_id = $node['parent'];
}
return array_reverse($path); // 根在前
}
$breadcrumb = build_ancestors_iterative($areas, 8);
// 同樣得到: 北京 → 海澱 → 上地
?>
| 方法 | 時間複雜度 | 空間複雜度 | 適用場景 |
|---|---|---|---|
| 遞歸子孫樹 | O(n²) 最壞 (每層遍歷全陣列) | O(h) 呼叫堆疊 | 資料量 < 1000、需完整樹結構輸出 |
| 遞歸祖先路徑 | O(n×h) h=樹高 | O(h) | 單次查詢、資料量小 |
| 迭代祖先路徑 | O(n + h) (預建 Map) | O(n) Map | 推薦:高頻查詢、大資料量 |
| 資料庫遞歸 CTE | SQL 層面完成 | - | MySQL 8.0+ / PostgreSQL / SQL Server |
-- 查詢節點 8 的所有祖先
WITH RECURSIVE ancestors AS (
SELECT id, name, parent, 1 AS level
FROM areas WHERE id = 8
UNION ALL
SELECT a.id, a.name, a.parent, anc.level + 1
FROM areas a
JOIN ancestors anc ON a.id = anc.parent
)
SELECT * FROM ancestors ORDER BY level;
<?php
// 轉換為 zTree / Element UI Tree / Ant Design Tree 所需格式
function to_tree_select($tree) {
$result = [];
foreach ($tree as $node) {
$item = [
'id' => $node['id'],
'label' => $node['name'],
'value' => $node['id'],
];
if (!empty($node['children'])) {
$item['children'] = to_tree_select($node['children']);
}
$result[] = $item;
}
return $result;
}
$select_data = to_tree_select($tree);
// 輸出 JSON 給前端
echo json_encode($select_data, JSON_UNESCAPED_UNICODE);
?>
parent_id 存在、無循環引用parent 路徑 (或僅更新父 ID,路徑由應用層計算)Closure Table (關係閉包表) 或 Nested Set (嵌套集合) 模型