PHP實現無限極分類的兩種方式,遞迴和引用
https://blog.csdn.net/falcom_fans/article/details/75579663
說到無限極分類,比較常見的做法是在建表的時候,增加一個PID欄位用來區別自己所屬的分類 由於展示資料的時候,需要表達出這種所屬關係,所以必然要在讀取資料的時候進行一系列處理,由此就牽涉到了兩種演算法
國民級演算法——遞迴
從資料庫取得二維陣列省略,遞迴的思路其實很簡單,遍歷陣列,根據每條資料的id值去尋找所有pid值等於自己id值的資料,直到找不到為止,實際實現起來也是通俗易懂
function getTree($arr,$pid=0,$level=0) { static $list = []; foreach ($arr as $key => $value) { if ($value["auth_pid"] == $pid) { $value["level"] = $level; $list[] = $value; unset($arr[$key]); //刪除已經排好的資料為了減少遍歷的次數,當然遞迴本身就很費神就是了 getTree($arr,$value["id"],$level+1); } } return $list; }
基本也沒啥好說的,這裡返回去的是一個已經排序好的一維陣列,展示的時候直接遍歷就好,加入level欄位是為了展示的時候,如果需要縮排,可以有個依據
巧妙的引用演算法
上面的遞迴原理通俗易懂,但是總所周知的原因,遞迴對資源的消耗是非常大的,實際執行起來效率也很低,所以有了下面的通過引用演算法
function generateTree($data){ $items = array(); foreach($data as $v){ $items[$v['auth_id']] = $v; } $tree = array(); foreach($items as $k => $item){ if(isset($items[$item['auth_pid']])){ $items[$item['auth_pid']]['son'][] = &$items[$k]; }else{ $tree[] = &$items[$k]; } } return $tree; }
是不是感覺有點謎之暈,慢慢分析這一段程式碼 整個方法大體分成兩個部分 第一部分是
$items = array();
foreach($data as $v){
$items[$v['auth_id']] = $v;
}
這一段應該是很通俗的,就是構建一個新的陣列,新陣列的key值是自己的主鍵id值 進行完這一步之後,應該得到的陣列形式是這樣額
Array ( [100] => Array ( [auth_id] => 100 [auth_name] => 後臺首頁 [auth_pid] => 0 ) [116] => Array ( [auth_id] => 116 [auth_name] => 管理員 [auth_pid] => 0 ) [120] => Array ( [auth_id] => 120 [auth_name] => 管理員列表 [auth_pid] => 116 ) [121] => Array ( [auth_id] => 121 [auth_name] => 管理員新增 [auth_pid] => 116 ) [122] => Array ( [auth_id] => 122 [auth_name] => 資料一覽 [auth_pid] => 100 ) [123] => Array ( [auth_id] => 123 [auth_name] => 更新日誌 [auth_pid] => 100 ) )
至於為什麼要特地多一次遍歷來將陣列的KEY值重構,這裡就是第二部分的巧妙之處了
$tree = array();
foreach($items as $k => $item){
if(isset($items[$item['auth_pid']])){
$items[$item['auth_pid']]['son'][] = &$items[$k];
}else{
$tree[] = &$items[$k];
}
}
return $tree;
慢慢的分析一下,這段程式碼將已經重構的陣列遍歷 並判斷當前陣列元素的父級分類是否存在 舉個例子 foreach第一次迴圈的時候
$k = 100;
$item = Array
(
[auth_id] => 100
[auth_name] => 後臺首頁
[auth_pid] => 0
)
$items[$item['auth_pid']] = $items[0]; //不存在鍵值為0的陣列元素,證明是頂級分類
isset($items[$item['auth_pid']]) = false;
$tree[] = &$items[$k];
注意到這裡,是採取引用的方式,為什麼呢?因為後面,其實我們的陣列元素是會變化的 當foreach第三次迴圈的時候,同樣分析
$k = 120;
$item = Array
(
[auth_id] => 120
[auth_name] => 管理員列表
[auth_pid] => 116
)
$items[$item['auth_pid']] = $items[116];
isset($items[$item['auth_pid']]) = true; //存在鍵值為116的陣列元素,證明這個元素是鍵值116元素的子分類
$items[$item['auth_pid']]['son'][] = &$items[$k];//給鍵值為116的陣列元素增加一個son鍵,並將當前遍歷的這個元素賦值給這個鍵
這裡也是採取了引用,還是那個原因,因為當前遍歷的元素很有可能還有子分類,當有子分類的時候,按照這個演算法,他自己還要增加son這個鍵,所以採用引用賦值的方式,可以保證自己的結構是完美的
整體來看這個演算法,如果加上兩句輸出來看一下
$tree = array();
foreach($items as $k => $item){
if(isset($items[$item['auth_pid']])){
$items[$item['auth_pid']]['son'][] = &$items[$k];
echo "1111<br>";
}else{
echo "2222<br>";
$tree[] = &$items[$k];
}
}
那麼會得到這樣的結果
2222
2222
1111
1111
1111
1111
可以看到,其實$tree裡面只有兩個陣列元素,而這兩個陣列元素是帶有son鍵的,在son鍵裡面儲存著自己的所有後代元素 進行完這個演算法之後,得到的結果會是這樣的
Array
(
[0] => Array
(
[auth_id] => 100
[auth_name] => 後臺首頁
[auth_pid] => 0
[son] => Array
(
[0] => Array
(
[auth_id] => 122
[auth_name] => 資料一覽
[auth_pid] => 100
)
[1] => Array
(
[auth_id] => 123
[auth_name] => 更新日誌
[auth_pid] => 100
)
)
)
[1] => Array
(
[auth_id] => 116
[auth_name] => 管理員
[auth_pid] => 0
[son] => Array
(
[0] => Array
(
[auth_id] => 120
[auth_name] => 管理員列表
[auth_pid] => 116
)
[1] => Array
(
[auth_id] => 121
[auth_name] => 管理員新增
[auth_pid] => 116
)
)
)
)
這個結果可以很方便的採用json的方式返回給前臺,或者接下來採用遞迴的方式使其變成一維陣列,都是很方便的,整個方法的精妙處就在於引用的使用
雖然這個例子使用的只有二級分類,實際上無論幾級分類都是很完美的,而且在執行速度上可以說都是很快的,從時間複雜度來說只是一個for迴圈,比遞迴不知道高到哪裡去了!