无限级分类的一种实现方式
list返回子孙树列表
/**
* * list返回子孙树
* @param array $list 原始列表
* @param int $parentId 父id
* @param int $level 几级子孙
* @param string $space 空格占位
* @date:2019.06.20
* @return array
*/
function treeList($list, $parentId = 0, $level = 0, $space = ' ')
{
static $bk = array();
foreach ($list as $each) {
if ($each['parent_id'] == $parentId) {
$each['level'] = $level;
$each['show'] = str_repeat($space, $level) . $each['name'];
$bk[$each['id']] = $each;
treeList($list, $each['id'], $level + 1, $space);//子孙紧跟在自己后面
}
}
return $bk;
}
list返回子孙树数组
/**
* * list返回子孙树
* @param array $list 原始列表
* @param int $parentId 父id
* @param int $level 几级子孙
* @param string $space 空格占位
* @date:2019.06.20
* @return array
*/
function treeArr($list, $parentId = 0, $level = 0, $space = ' ')
{
$bk = array();
foreach ($list as $each) {
if ($each['parent_id'] == $parentId) {
$each['level'] = $level;
$each['show'] = str_repeat($space, $level) . $each['name'];
$each['sonArr'] = treeArr($list, $each['id'], $level + 1, $space);
$bk[$each['id']] = $each;
}
}
return $bk;
}
如果只需要所有层级名称
/**
* 获取所有层级名列表
* @date:2019.06.20
* @return array
*/
function getFulNameList()
{
$listRaw = getList();
$list = array();
//一些处理
foreach ($listRaw as $each) {
$list[$each['id']] = iconvArray($each, 'GBK', 'UTF-8');//GBK->UTF-8
}
//加所有层级名
foreach ($list as $id => $each) {
$fulName = $list['name'];
$parentId = $each['parent_id'];
while ($parentId != 0 && $list[$parentId]['parent_id'] != 0) {
$fulName = $list[$parentId]['name'] . '_' . $fulName;
$parentId = $list[$parentId]['parent_id'];
}
$list[$id]['fulName'] = $fulName;
}
return $list;
}
获取所有子孙
function getSpringId($selfId, $list)
{
$springIdArr = array();
$parentIdArr = array($selfId => $selfId);
do {
$newSonArr = array();
foreach ($list as $each) {
if (isset($parentIdArr[$each['parent_id']])) {
$springIdArr[$each['id']] = $each['id'];
$newSonArr[$each['id']] = $each['id'];
}
}
$parentIdArr = $newSonArr;//新一辈父
} while (count($parentIdArr) > 0);
return $springIdArr;
}
提供一个无限极分类的方法
public static function list_level($data, $pid = 0, $level = 0)
{
//定义一个静态变量,存储一个空数组,用静态变量,是因为静态变量不会被销毁,
//会保存之前保留的值,普通变量在函数结束时,会死亡,生命周期函数开始到函数结束,再次调用重新开始生长
//保存一个空数组
static $array = [];
foreach ($data as $k => $v) {
//进行判断如果pid=0,那么为顶级父类,放入定义的空数组里
if ($pid == $v['pid']) {
//添加空格进行分层
$v['level'] = $level;
$array[] = $v;
//递归点,调用自身,把顶级父类的主键id作为父类进行再调用循环,空格+1
self::list_level($data, $v->id, $level + 1);
}
}
return $array;
}
案例
无限极分类的设计和实现,比较常见的做法是在建表的时候,增加一个PID字段用来区别自己所属的分类
// 数据
$array = array(
array('id' => 1, 'pid' => 0, 'name' => '河北省'),
array('id' => 2, 'pid' => 0, 'name' => '北京市'),
array('id' => 3, 'pid' => 1, 'name' => '邯郸市'),
array('id' => 4, 'pid' => 2, 'name' => '朝阳区'),
array('id' => 5, 'pid' => 2, 'name' => '通州区'),
array('id' => 6, 'pid' => 4, 'name' => '望京'),
array('id' => 7, 'pid' => 4, 'name' => '酒仙桥'),
array('id' => 8, 'pid' => 3, 'name' => '永年区'),
array('id' => 9, 'pid' => 1, 'name' => '武安市'),
);
据在数据库中存储大概是这个样子,怎么实现无限极递归呢,有两种常用的做法,递归和引用算法
递归算法
/**
* 递归实现无限极分类
* @param $array 分类数据
* @param $pid 父ID
* @param $level 分类级别
* @return $list 分好类的数组 直接遍历即可 $level可以用来遍历缩进
*/
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;
}
/*
* 获得递归完的数据,遍历生成分类
*/
$array = getTree($array);
foreach ($array as $value) {
echo str_repeat('--', $value['level']), $value['name'] . '<br />';
}
输出结果 无限极分类实现ok
河北省
--邯郸市
----永年区
--武安市
北京市
--朝阳区
----望京
----酒仙桥
--通州区
递归的思路其实很简单,遍历数组,根据每条数据的id值去寻找所有pid值等于自己id值的数据,直到找不到为止,实际实现起来也是通俗易懂,但是总所周知的原因,递归对资源的消耗是非常大的,实际执行起来效率也很低,所以有了下面的通过引用算法
引用算法
function generateTree($array)
{
//第一步 构造数据
$items = array();
foreach ($array as $value) {
$items[$value['id']] = $value;
}
//第二步 遍历数据 生成树状结构
$tree = array();
foreach ($items as $key => $value) {
if (isset($items[$item['pid']])) {
$items[$items['pid']]['son'][] = &$items[$key];
} else {
$tree[] = &$items[$key];
}
}
return $tree;
}
经过第一步 数据变成了这样
Array
(
[1] => Array
(
[id] => 1
[pid] => 0
[name] => 河北省
[children] => Array
(
)
)
[2] => Array
(
[id] => 2
[pid] => 0
[name] => 北京市
[children] => Array
(
)
)
[3] => Array
(
[id] => 3
[pid] => 1
[name] => 邯郸市
[children] => Array
(
)
)
[4] => Array
(
[id] => 4
[pid] => 2
[name] => 朝阳区
[children] => Array
(
)
)
[5] => Array
(
[id] => 5
[pid] => 2
[name] => 通州区
[children] => Array
(
)
)
[6] => Array
(
[id] => 6
[pid] => 4
[name] => 望京
[children] => Array
(
)
)
[7] => Array
(
[id] => 7
[pid] => 4
[name] => 酒仙桥
[children] => Array
(
)
)
[8] => Array
(
[id] => 8
[pid] => 3
[name] => 永年区
[children] => Array
(
)
)
[9] => Array
(
[id] => 9
[pid] => 1
[name] => 武安市
[children] => Array
(
)
)
)
第一步很容易就能看懂,就是构造数据,现在咱们仔细说一下第二步
$tree = array();
//遍历构造的数据
foreach($items as $key => $value){
//如果pid这个节点存在
if(isset($items[$value['pid']])){
//把当前的$value放到pid节点的son中 注意 这里传递的是引用 为什么呢?
$items[$value['pid']]['son'][] = &$items[$key];
}else{
$tree[] = &$items[$key];
}
}
这个方法的核心在于引用,php变量默认的传值方式是按指传递
也就是说 假如说 遍历顺序是 河北省 邯郸市 当遍历到河北省时 会把河北省放到$tree
中 遍历到邯郸市时 会把邯郸市放到河北省的子节点数组中
但是!!! 这会儿的$tree
数组中 河北省已经放进去了 根据php变量按值传递的规则 你并没有更改$tree
数组中的河北省的数据 所以这里用到了引用传递
当你对河北省做更改时,$tree
数组中的河北省也一并做了更改
下面我们做个实验 我们把引用传递去掉,看一下结果
使用普通传值输出结果
Array
(
[0] => Array
(
[id] => 1
[pid] => 0
[name] => 河北省
)
[1] => Array
(
[id] => 2
[pid] => 0
[name] => 北京市
)
)
可以看到 只有河北省和北京市输出出来了 因为他们俩是第一级节点 而且排行1和2,放到$tree
数组中之后,没有使用引用传递,那么后续对他俩的子节点的操作都没有在$tree
中生效,现在我们更改一下顺序 把邯郸市放到河北省的前面 那么根据咱们的推断 那么邯郸市就应该出现在$tree
数组里
邯郸市放到河北省前面的输出结果
Array
(
[0] => Array
(
[id] => 1
[pid] => 0
[name] => 河北省
[son] => Array
(
[0] => Array
(
[id] => 3
[pid] => 1
[name] => 邯郸市
)
)
)
[1] => Array
(
[id] => 2
[pid] => 0
[name] => 北京市
)
)
果然是这样 那么证明我们的推断是正确的 现在我们把引用传值改回去 再看一下
使用引用传值输出结果
Array
(
[1] => Array
(
[id] => 1
[pid] => 0
[name] => 河北省
[children] => Array
(
[0] => Array
(
[id] => 3
[pid] => 1
[name] => 邯郸市
[children] => Array
(
[0] => Array
(
[id] => 8
[pid] => 3
[name] => 永年区
)
)
)
[1] => Array
(
[id] => 9
[pid] => 1
[name] => 武安市
)
)
)
[2] => Array
(
[id] => 2
[pid] => 0
[name] => 北京市
[children] => Array
(
[0] => Array
(
[id] => 4
[pid] => 2
[name] => 朝阳区
[children] => Array
(
[0] => Array
(
[id] => 6
[pid] => 4
[name] => 望京
)
[1] => Array
(
[id] => 7
[pid] => 4
[name] => 酒仙桥
)
)
)
[1] => Array
(
[id] => 5
[pid] => 2
[name] => 通州区
)
)
)
)
树状结构完美的输出出来了 这个方法的核心就是引用传值
相关参考:
https://www.php.cn/php-weizijiaocheng-353267.html
https://www.cnblogs.com/phpk/p/10929918.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通