PHP实现无限极分类的方式
假如你面试的时候被问到无限极分类的设计和实现,常见的方法是在建表或者数据的时候,增加一个[pid或者parent_id]字段用来区别自己所属的分类
譬如
数据表设计模板参考
id(int) | pid(int) | name(varchar) | … |
---|---|---|---|
1 | 0 | 1级 | |
2 | 0 | 1级 | |
3 | 1 | 2级 |
select出来的数据就是如下,怎么实现无限极递归呢,有两种常用的做法,递归和引用算法
$array = array(
array('id' => 1, 'pid' => 0, 'name' => '1级'),
array('id' => 2, 'pid' => 0, 'name' => '1级'),
array('id' => 3, 'pid' => 1, 'name' => '2级'),
array('id' => 4, 'pid' => 2, 'name' => '2级'),
array('id' => 5, 'pid' => 2, 'name' => '2级'),
array('id' => 6, 'pid' => 4, 'name' => '3级'),
array('id' => 7, 'pid' => 4, 'name' => '3级'),
array('id' => 8, 'pid' => 3, 'name' => '3级'),
array('id' => 9, 'pid' => 1, 'name' => '2级'),
array('id' => 10, 'pid' => 8, 'name' => '4级'),
array('id' => 11, 'pid' => 10, 'name' => '5级'),
array('id' => 12, 'pid' => 11, 'name' => '6级'),
//...
);
递归算法
function getTree($array, $pid =0, $level = 0){
//声明静态数组,避免递归调用时,多次声明导致数组覆盖
static $list = [];
foreach ($array as $key => $value){
//第一次遍历,找到父节点为根节点的节点 也就是pid=0的节点
if ($value['pid'] == $pid){
//父节点为根节点的节点,级别为0,也就是第一级
$value['level'] = $level;
//把数组放到list中
$list[] = $value;
//把这个节点从数组中移除,减少后续递归消耗
unset($array[$key]);
//开始递归,查找父ID为该节点ID的节点,级别则为原级别+1
getTree($array, $value['id'], $level+1);
}
}
return $list;
}
/************** 调用递归方法后遍历新数组 ****************/
$tree1 = getTree($array);
foreach($tree1 as $val){
echo str_repeat('--', $val['level']).$val['name'].'<br />';
}
得到的数据如下
Array
(
[0] => Array
(
[id] => 1
[pid] => 0
[name] => 1级
[level] => 0
)
[1] => Array
(
[id] => 3
[pid] => 1
[name] => 2级
[level] => 1
)
[2] => Array
(
[id] => 8
[pid] => 3
[name] => 3级
[level] => 2
)
[3] => Array
(
[id] => 10
[pid] => 8
[name] => 4级
[level] => 3
)
[4] => Array
(
[id] => 11
[pid] => 10
[name] => 5级
[level] => 4
)
[5] => Array
(
[id] => 12
[pid] => 11
[name] => 6级
[level] => 5
)
[6] => Array
(
[id] => 9
[pid] => 1
[name] => 2级
[level] => 1
)
[7] => Array
(
[id] => 2
[pid] => 0
[name] => 1级
[level] => 0
)
[8] => Array
(
[id] => 4
[pid] => 2
[name] => 2级
[level] => 1
)
[9] => Array
(
[id] => 6
[pid] => 4
[name] => 3级
[level] => 2
)
[10] => Array
(
[id] => 7
[pid] => 4
[name] => 3级
[level] => 2
)
[11] => Array
(
[id] => 5
[pid] => 2
[name] => 2级
[level] => 1
)
)
遍历后的结果如下
1级
–2级
----3级
------4级
--------5级
----------6级
–2级
1级
–2级
----3级
----3级
–2级
接下来是递归算法
//引用算法
function generateTree($array){
//第一步 构造数据
$items = array();
foreach($array as $value){
$items[$value['id']] = $value;
}
//第二步 遍历数据 生成树状结构
$tree = array();
foreach($items as $key => $value){
if(isset($items[$value['pid']])){
//存在父级
/************** 引用了$items[$value['pid']] 所以$items[$value['pid']]改变,$tree的关联的键元素也会发生改变 ****************/
/************** 看不懂这一步的建议去了解一下引用的原理 ****************/
$items[$value['pid']]['children'][] = &$items[$key];
}else{
//不存在父级的顶级元素才会走到这一步
/************** 看不懂这一步的建议去了解一下引用的原理 ****************/
$tree[] = &$items[$key];
}
}
return $tree;
}
$tree2 = generateTree($array);
得到的数据如下
Array
(
[0] => Array
(
[id] => 1
[pid] => 0
[name] => 1级
[children] => Array
(
[0] => Array
(
[id] => 3
[pid] => 1
[name] => 2级
[children] => Array
(
[0] => Array
(
[id] => 8
[pid] => 3
[name] => 3级
[children] => Array
(
[0] => Array
(
[id] => 10
[pid] => 8
[name] => 4级
[children] => Array
(
[0] => Array
(
[id] => 11
[pid] => 10
[name] => 5级
[children] => Array
(
[0] => Array
(
[id] => 12
[pid] => 11
[name] => 6级
)
)
)
)
)
)
)
)
)
[1] => Array
(
[id] => 9
[pid] => 1
[name] => 2级
)
)
)
[1] => Array
(
[id] => 2
[pid] => 0
[name] => 1级
[children] => Array
(
[0] => Array
(
[id] => 4
[pid] => 2
[name] => 2级
[children] => Array
(
[0] => Array
(
[id] => 6
[pid] => 4
[name] => 3级
)
[1] => Array
(
[id] => 7
[pid] => 4
[name] => 3级
)
)
)
[1] => Array
(
[id] => 5
[pid] => 2
[name] => 2级
)
)
)
)
引用算法的遍历方法如下
function ergoTree($arr,$level=0){
foreach ($arr as $item){
if(isset($item['children'])){
echo str_repeat('--', $level).$item['name']."<br>";
ergoTree($item['children'],$level+1);
}else{
echo str_repeat('--', $level).$item['name']."<br>";
}
}
}
ergoTree($tree2);
得到相同的结果
1级
–2级
----3级
------4级
--------5级
----------6级
–2级
1级
–2级
----3级
----3级
–2级
原理分析
通过第一次遍历$items后得到的是以下的数据
Array
(
[1] => Array
(
[id] => 1
[pid] => 0
[name] => 1级
)
[2] => Array
(
[id] => 2
[pid] => 0
[name] => 1级
)
[3] => Array
(
[id] => 3
[pid] => 1
[name] => 2级
)
[4] => Array
(
[id] => 4
[pid] => 2
[name] => 2级
)
[5] => Array
(
[id] => 5
[pid] => 2
[name] => 2级
)
[6] => Array
(
[id] => 6
[pid] => 4
[name] => 3级
)
[7] => Array
(
[id] => 7
[pid] => 4
[name] => 3级
)
[8] => Array
(
[id] => 8
[pid] => 3
[name] => 3级
)
[9] => Array
(
[id] => 9
[pid] => 1
[name] => 2级
)
[10] => Array
(
[id] => 10
[pid] => 8
[name] => 4级
)
[11] => Array
(
[id] => 11
[pid] => 10
[name] => 5级
)
[12] => Array
(
[id] => 12
[pid] => 11
[name] => 6级
)
)
然后再将这组新的数组进行第二次遍历
遍历时走到第一个元素后判断到该元素的$key['pid']并不存在$items中,所以$tree[]直接引用$items[$key],同理第二个也是一样为一级分类。
遍历走到第三个的时候判断到该元素的pid是$items[1]的子元素,那么直接将第三个元素的数据引用到$items[1]['children']中。
那么此时的$items和$tree会变成如下
//这是$items的
Array
(
[1] => Array
(
[id] => 1
[pid] => 0
[name] => 1级
[children] => Array
(
[0] => Array
(
[id] => 3
[pid] => 1
[name] => 2级
)
)
)
[2] => Array
(
[id] => 2
[pid] => 0
[name] => 1级
)
[3] => Array
(
[id] => 3
[pid] => 1
[name] => 2级
)
...
//这是$tree的
Array
(
[0] => Array
(
[id] => 1
[pid] => 0
[name] => 1级
[children] => Array
(
[0] => Array
(
[id] => 3
[pid] => 1
[name] => 2级
)
)
)
[1] => Array
(
[id] => 2
[pid] => 0
[name] => 1级
)
)
/*
* 为什么这儿的$tree[0][1]的元素会变成同$items[1]的会变成一样的,去了解一下 & 引用你就明白了
* 同理,$items[1]['children'][]引用了$items[$key],就算$items[$key]同样有子元素,只要$items[$key]添加['children']数据内容改变,$items[1]['children'][]内的数据会跟着发生改变!
* */
Array
(
[1] => Array
(
[id] => 1
[pid] => 0
[name] => 1级
[children] => Array
(
[0] => Array
(
[id] => 3
[pid] => 1
[name] => 2级
[children] => Array
(
[0] => Array
(
[id] => 8
[pid] => 3
[name] => 3级
)
)
)
...
[3] => Array
(
[id] => 3
[pid] => 1
[name] => 2级
[children] => Array
(
[0] => Array
(
[id] => 8
[pid] => 3
[name] => 3级
)
)
)
//对比数据就能理解了
写得不好,纯手打编辑,欢迎纠错!!