首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 實現無限級分類的遞歸與非遞歸方案詳解

     編輯:PHP 相關知識

PHP 無限級分類實現方案全解析

在 Web 開發中,無限級分類(如商品分類、評論回覆、組織架構)是常見需求。本文將介紹兩種主流實現方式:遞歸法與非遞歸(迭代)法,並附上完整代碼示例與性能對比。

一、資料庫表結構設計

核心欄位包含:id(主鍵)、pid(父級 ID)、name(分類名稱)、deep(層級深度,冗餘字段用於優化查詢)。

<?php
// 建表示例 SQL
CREATE TABLE `category` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pid` int(11) DEFAULT '0' COMMENT '父級ID',
  `deep` tinyint(4) DEFAULT '0' COMMENT '層級深度',
  `name` varchar(50) NOT NULL COMMENT '分類名稱',
  PRIMARY KEY (`id`),
  KEY `pid` (`pid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
?>

二、遞歸實現(經典寫法)

遞歸代碼簡潔,邏輯清晰,適合數據量較小(< 1000 條)的場景。

<?php
/**
 * 遞歸獲取樹狀結構
 * @param array $list 扁平化數據
 * @param int $pid 父級ID
 * @return array
 */
function getTreeRecursive(array $list, int $pid = 0): array
{
    $tree = [];
    foreach ($list as $row) {
        if ($row['pid'] == $pid) {
            $children = getTreeRecursive($list, $row['id']);
            if ($children) {
                $row['children'] = $children;
            }
            $tree[$row['id']] = $row;
        }
    }
    return $tree;
}

// 使用示例
$list = [
    ['id'=>1, 'pid'=>0, 'name'=>'電子產品'],
    ['id'=>2, 'pid'=>1, 'name'=>'手機'],
    ['id'=>3, 'pid'=>2, 'name'=>'蘋果'],
    ['id'=>4, 'pid'=>0, 'name'=>'圖書'],
];
$tree = getTreeRecursive($list);
print_r($tree);
?>

三、非遞歸實現(引用賦值法)

利用 PHP 變量引用特性,一次遍歷即可構建樹形結構,性能顯著優於遞歸,適合大數據量場景。

<?php
/**
 * 非遞歸構建無限級分類
 * @param array $list 扁平化數據(需包含 id、pid)
 * @return array 樹形結構數組
 */
function buildTreeIterative(array $list): array
{
    // 1. 建立 ID 索引映射
    $map = [];
    foreach ($list as $item) {
        $map[$item['id']] = $item;
    }

    // 2. 利用引用構建父子關係
    $tree = [];
    foreach ($map as $id => &$node) {
        $pid = $node['pid'];
        if ($pid == 0 || !isset($map[$pid])) {
            // 頂級分類
            $tree[$id] = &$node;
        } else {
            // 子分類,掛載到父節點 children
            if (!isset($map[$pid]['children'])) {
                $map[$pid]['children'] = [];
            }
            $map[$pid]['children'][$id] = &$node;
        }
    }
    unset($node); // 釋放引用
    return $tree;
}

// 測試數據
$list = [
    ['id'=>1, 'pid'=>0, 'name'=>'電子產品'],
    ['id'=>2, 'pid'=>1, 'name'=>'手機'],
    ['id'=>3, 'pid'=>2, 'name'=>'蘋果'],
    ['id'=>4, 'pid'=>1, 'name'=>'電腦'],
    ['id'=>5, 'pid'=>0, 'name'=>'圖書'],
];
$tree = buildTreeIterative($list);
echo json_encode($tree, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
?>

四、前端下拉選單渲染(層級縮進顯示)

結合遞歸生成 <select> 選項,使用 &nbsp; 實現視覺縮進。

<?php
function renderSelectOptions(array $tree, int $level = 0): string
{
    $html = '';
    $indent = str_repeat('&nbsp;&nbsp;|--', $level);
    foreach ($tree as $node) {
        $html .= "<option value='{$node['id']}'>{$indent}{$node['name']}</option>";
        if (!empty($node['children'])) {
            $html .= renderSelectOptions($node['children'], $level + 1);
        }
    }
    return $html;
}

// 輸出完整下拉框
echo "<select name='category_id'>";
echo renderSelectOptions($tree);
echo "</select>";
?>

五、性能對比與選型建議

方案時間複雜度適用場景優點缺點
遞歸O(n²) 最壞情況數據量小、層級淺代碼簡單、易理解大數據量時棧溢出風險、性能差
非遞歸(引用法)O(n)數據量大、高併發性能極佳、單次遍歷代碼稍複雜、需理解引用機制

六、進階優化建議

  • 路徑枚舉法:在表中增加 path 字段(如 0/1/2/3),查詢子孫節點只需 LIKE '0/1/2/%',極大提升讀取性能。
  • 閉包表:單獨建立關係表存儲所有祖先後代關係,適合極其複雜的層級查詢。
  • 緩存策略:分類數據變更頻率低,建議使用 Redis 緩存完整樹結構,更新時重建緩存。

總結:中小型項目首選非遞歸引用法,兼顧性能與可維護性;大型系統可結合路徑枚舉+緩存方案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved