在電商、CMS 等系統中,分類結構通常採用「父子級關聯」設計(如 parent_cid 指向父分類 ID)。本文介紹如何通過遞歸算法高效獲取指定分類下的所有子孫分類 ID,並結合緩存機制優化效能。
分類表結構範例:
CREATE TABLE item_cat_info (
cid INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
parent_cid INT DEFAULT 0,
root_cid INT DEFAULT 0,
KEY idx_parent (parent_cid)
);
數據樣本:
cid | name | parent_cid | root_cid
----|------------|------------|---------
1 | 電子產品 | 0 | 0
2 | 手機 | 1 | 1
3 | 蘋果手機 | 2 | 1
4 | 華為手機 | 2 | 1
5 | 電腦 | 1 | 1
6 | 筆記本 | 5 | 1
/**
* 遞歸獲取指定分類下所有子孫分類 ID
*
* @param array $categories 分類數據陣列 [cid => ['cid', 'parent_cid', ...]]
* @param int $parentId 父分類 ID
* @param int $level 當前遞歸層級(內部使用)
* @param bool $resetCache 是否重置靜態緩存
* @return array|false 子孫分類 ID 陣列,失敗返回 false
*/
static function getMenuTree(array $categories, int $parentId = 0, int $level = 0, bool $resetCache = true): array|false {
static $tree = [];
// 重置緩存(首次調用或強制重置時)
if ($resetCache) {
$tree = [];
}
if (empty($categories)) {
return false;
}
$level++;
// 第一層記錄根節點
if ($level === 1) {
$tree[] = $parentId;
}
foreach ($categories as $key => $category) {
if ($category['parent_cid'] == $parentId) {
$tree[] = $category['cid'];
// 移除已處理節點,減少後續遍歷開銷
unset($categories[$key]);
// 遞歸處理子分類
self::getMenuTree($categories, $category['cid'], $level, false);
}
}
return $tree;
}
// 假設從資料庫獲取所有分類
$allCategories = [
1 => ['cid' => 1, 'name' => '電子產品', 'parent_cid' => 0, 'root_cid' => 0],
2 => ['cid' => 2, 'name' => '手機', 'parent_cid' => 1, 'root_cid' => 1],
3 => ['cid' => 3, 'name' => '蘋果手機', 'parent_cid' => 2, 'root_cid' => 1],
4 => ['cid' => 4, 'name' => '華為手機', 'parent_cid' => 2, 'root_cid' => 1],
5 => ['cid' => 5, 'name' => '電腦', 'parent_cid' => 1, 'root_cid' => 1],
6 => ['cid' => 6, 'name' => '筆記本', 'parent_cid' => 5, 'root_cid' => 1],
];
// 獲取「手機」(cid=2) 下所有子分類
$childIds = getMenuTree($allCategories, 2);
// 結果:[2, 3, 4] (包含自身 ID 2)
// 獲取頂級分類「電子產品」(cid=1) 下所有子分類
$allChildIds = getMenuTree($allCategories, 1);
// 結果:[1, 2, 3, 4, 5, 6]
避免每次請求都查詢資料庫,將分類數據導出為 PHP 陣列文件:
public function actionExportCategoryCache(): void {
$filePath = Yii::getPathOfAlias('application') . '/data/itemcat.php';
$db = Yii::app()->db;
$rows = $db->createCommand(
"SELECT cid, name, parent_cid, root_cid FROM item_cat_info"
)->queryAll();
$categories = [];
foreach ($rows as $row) {
$categories[$row['cid']] = [
'cid' => $row['cid'],
'name' => $row['name'],
'parent_cid' => $row['parent_cid'],
'root_cid' => $row['root_cid'],
];
}
// 生成可直接 require 的 PHP 文件
$content = "
$categories = require('/path/to/data/itemcat.php');
$childIds = getMenuTree($categories, $targetParentId);
$categories 以引用傳遞 &$categories,避免陣列複製開銷parent_cid => [子分類列表] 的映射表,將 O(n²) 降為 O(n)function getMenuTreeIterative(array $categories, int $parentId): array {
// 建立父子映射索引
$childrenMap = [];
foreach ($categories as $cat) {
$childrenMap[$cat['parent_cid']][] = $cat['cid'];
}
$result = [$parentId];
$stack = [$parentId];
while (!empty($stack)) {
$current = array_pop($stack);
if (isset($childrenMap[$current])) {
foreach ($childrenMap[$current] as $childId) {
$result[] = $childId;
$stack[] = $childId;
}
}
}
return $result;
}
遞歸獲取分類樹是經典樹形結構遍歷問題。關鍵優化點在於:靜態變量緩存結果、unset 移除已處理節點減少遍歷、配合文件緩存避免重複查庫。對於超大規模或極深層級分類,建議採用非遞歸迭代版本並預建立父子映射索引。