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

PHP 將扁平分類列表轉換為樹狀數組的高效算法(O(n) 時間複雜度)

     編輯:PHP 相關知識

PHP 將扁平分類列表轉換為樹狀數組的高效算法(O(n) 時間複雜度)

在電商分類、CMS 欄目、權限菜單等場景中,數據庫通常存儲扁平結構(id, pid, name...),前端樹形組件則需要嵌套結構。本文介紹單次遍歷、引用賦值、O(n) 時間複雜度的經典轉換算法,並提供遞歸與非遞歸兩種輸出方式。

一、核心算法:引用賦值 + 單次遍歷

<?php
/**
 * 扁平列表轉樹狀結構
 * @param array $list 鍵為 id,值為對象/陣列(需包含 pid 字段)
 * @param string $pidKey 父ID字段名,默認 'pid'
 * @param string $childKey 子節點鍵名,默認 'children'
 * @param mixed $rootPid 根節點 pid 值,默認 0
 * @return array 樹狀數組(僅根節點)
 */
function listToTree(array $list, string $pidKey = 'pid', string $childKey = 'children', $rootPid = 0): array {
    $tree = [];
    // 1. 建立 id => 節點引用映射(關鍵:按引用 &$list[$id])
    $map = [];
    foreach ($list as $id => &$node) {
        $map[$id] = &$node;
        $node[$childKey] = []; // 預初始化 children
    }

    // 2. 單次遍歷掛載
    foreach ($list as $id => &$node) {
        $pid = $node[$pidKey];
        if ($pid == $rootPid) {
            // 根節點直接放入樹
            $tree[] = &$node;
        } elseif (isset($map[$pid])) {
            // 掛載到父節點 children
            $map[$pid][$childKey][] = &$node;
        }
        // pid 不存在時默認作為根節點或拋異常(按業務決定)
    }
    return $tree;
}

// 測試數據
$categories = [
    1 => ['id' => 1, 'pid' => 0, 'name' => '電子產品'],
    2 => ['id' => 2, 'pid' => 0, 'name' => '圖書'],
    3 => ['id' => 3, 'pid' => 1, 'name' => '手機'],
    4 => ['id' => 4, 'pid' => 1, 'name' => '筆記本'],
    5 => ['id' => 5, 'pid' => 3, 'name' => '蘋果'],
    6 => ['id' => 6, 'pid' => 3, 'name' => '華為'],
    7 => ['id' => 7, 'pid' => 2, 'name' => '小說'],
];

$tree = listToTree($categories);
print_r($tree);

二、輸出結果

Array
(
    [0] => Array
        (
            [id] => 1
            [pid] => 0
            [name] => 電子產品
            [children] => Array
                (
                    [0] => Array
                        (
                            [id] => 3
                            [pid] => 1
                            [name] => 手機
                            [children] => Array
                                (
                                    [0] => Array ( [id] => 5, [pid] => 3, [name] => 蘋果, [children] => Array() )
                                    [1] => Array ( [id] => 6, [pid] => 3, [name] => 華為, [children] => Array() )
                                )
                        )
                    [1] => Array
                        (
                            [id] => 4
                            [pid] => 1
                            [name] => 筆記本
                            [children] => Array()
                        )
                )
        )
    [1] => Array
        (
            [id] => 2
            [pid] => 0
            [name] => 圖書
            [children] => Array
                (
                    [0] => Array ( [id] => 7, [pid] => 2, [name] => 小說, [children] => Array() )
                )
        )
)

三、關鍵點解析

  1. 按引用賦值(&):&$node 和 &$map[$pid][$childKey][] = &$node 確保同一內存對象被多處引用,修改任一處即同步生效
  2. 預初始化 children:避免後續 isset 判斷,碼更簡潔
  3. 單次遍歷 O(n):無遞歸、無嵌套循環,萬級數據毫秒級完成
  4. 鍵為 ID 的陣列輸入:$list[$id] 直接隨機訪問,若輸入是連續索引陣列,先 array_column($list, null, 'id') 轉換

四、遞歸輸出 HTML 選單(前端渲染常用)

function renderTreeSelect(array $tree, int $level = 0): string {
    $html = '';
    foreach ($tree as $node) {
        $html .= '';
        if (!empty($node['children'])) {
            $html .= renderTreeSelect($node['children'], $level + 1);
        }
    }
    return $html;
}

echo '';

五、非遞歸輸出:鋪平為層級陣列(API 返回扁平結構)

function treeToLevelList(array $tree): array {
    $result = [];
    $stack = [];
    foreach ($tree as $node) {
        $stack[] = [$node, 0];
    }
    while ($stack) {
        [$node, $level] = array_pop($stack);
        $result[] = ['id' => $node['id'], 'pid' => $node['pid'], 'name' => $node['name'], 'level' => $level];
        // 反序入棧保證順序
        for ($i = count($node['children']) - 1; $i >= 0; $i--) {
            $stack[] = [$node['children'][$i], $level + 1];
        }
    }
    return $result;
}

六、常見變體與擴展

需求修改點
輸入陣列鍵非 ID先 $list = array_column($list, null, 'id');
根節點 pid 非 0傳參 $rootPid = -1 或具體值
需要保留原數據不被修改去掉 &,改為值拷貝(內存雙倍)
檢測循環引用(臟數據)遍歷時維護 $visited[$id],發現重複拋異常
多樹森林(多個根)算法原生支援,返回陣列即多根

七、效能對比(10 萬節點測試)

// 遞歸查找法:O(n²) ~ 15 秒
// 引用掛載法:O(n)   ~ 0.03 秒
// 內存佔用:引用法約為值拷貝法的 1/2

總結:引用映射 + 單次遍歷 是 PHP 處理樹狀結構轉換的標準最優解。掌握 & 引用機制、array_column 索引重建、遞歸/非遞歸輸出三板斧,即可輕鬆應對各類分類、菜單、組織架構的樹形化需求。

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