搜索
首页后端开发php教程无限极分类的三种方式(迭代、递归、引用)

一般的分类树状结构有两种方式:

  • 一种是adjacency list,也就是是id,parent id这中形式。

  • 另一种是nested set,即左右值的形式。

左右值形式查询起来比较高效,无需递归等,推荐使用,但是没有pid形式简单直观,而且有些旧的数据库类似地区等结构设计一直是pid这种形式(貌似也有算法可以将两者转换,不做深入了解),所以。。。

下面所说的都为adjacency list的形式,数据表格式类似id,pid,name这种格式。

通常来说是将数据全部从数据库读取后,然后再组装数组来实现,当然也可以每次递归等都查询数据库,但是会造成数据库压力,且不容易封装成方法,不建议这样做。

目前来说常用的有三种方式,我们来实现select下拉菜单展示的样式:

1、首先是最常用最普通,同样也是效率最低的递归方法:就是不停的foreach循环递归。


function getTreeOptions3($list, $pid = 0)
{    $options = [];    foreach ($list as $key => $value) {        if ($value['id'] == $pid) {//查看是否为子元素,如果是则递归继续查询
            $options[$value['id']] = $value['name'];            unset($list[$key]);//销毁已查询的,减轻下次递归时查询数量
            $optionsTmp = $this->getTreeOptions3($list, $value['id']);//递归
            if (!empty($optionsTmp)) {                $options = array_merge($options, $optionsTmp);
            }
        }
    }    return $options;
}


2、第二种是利用入栈、出栈的递归来计算,效率比上个好点,但是也挺慢的。流程是先反转数组,然后取出顶级数组入栈,开始while循环,先出栈一个查找其下有没有子节点,如果有子节点,则将此子节点也入栈,下次while循环就会查子节点的,依次类推:


function getTreeOptions2($list, $pid = 0)
{    $tree = [];    if (!empty($list)) {        //先将数组反转,因为后期出栈时会优先出最上面的
        $list = array_reverse($list);        //先取出顶级的来压入数组$stack中,并将在$list中的删除掉
        $stack = [];        foreach ($list as $key => $value) {            if ($value['pid'] == $pid) {                array_push($stack,$value);                unset($list[$key]);
            }
        }        while (count($stack)) {            //先从栈中取出第一项
            $info = array_pop($stack);            //查询剩余的$list中pid与其id相等的,也就是查找其子节点
            foreach ($list as $key => $child) {                if ($child[pid] == $info['id']) {                    //如果有子节点则入栈,while循环中会继续查找子节点的下级
                    array_push($stack,  $child);                    unset($list[$key]);
                }
            }            //组装成下拉菜单格式
            $tree[$info['id']] = $info['name'];
        }
    }    return $tree;
}


3、利用引用来处理,这个真的很巧妙,而且效率最高,可参照这里:


/**
* 先生成类似下面的形式的数据
* [
*  'id'=>1,
*  'pid'=>0,
*  'items'=>[
*      'id'=>2,
*      'pid'=>'1'
*       。。。
*  ]
* ]*/function getTree($list, $pid = 0)
{    $tree = [];    if (!empty($list)) {        //先修改为以id为下标的列表
        $newList = [];        foreach ($list as $k => $v) {            $newList[$v['id']] = $v;
        }        //然后开始组装成特殊格式
        foreach ($newList as $value) {            if ($pid == $value['pid']) {//先取出顶级
                $tree[] = &$newList[$value['id']];
            } elseif (isset($newList[$value['pid']])) {//再判定非顶级的pid是否存在,如果存在,则再pid所在的数组下面加入一个字段items,来将本身存进去
                $newList[$value['pid']]['items'][] = &$newList[$value['id']];
            }
        }
    }    return $tree;
}


然后再递归生成select下拉菜单所需要的,由于上方的特殊格式,导致递归起来非常快:


function formatTree($tree)
{    $options = [];    if (!empty($tree)) {        foreach ($tree as $key => $value) {            $options[$value['id']] = $value['name'];            if (isset($value['items'])) {//查询是否有子节点
                $optionsTmp = $this->formatTree($value['items']);                if (!empty($optionsTmp)) {                    $options = array_merge($options, $optionsTmp);
                }
            }
        }
    }    return $options;
}


以上三种,对于数据量小的来说,无所谓用哪种,但是对于数据量大的来说就非常明显了,用4000条地区数据测试结果效率对比:

  • 第一种方法(递归)耗时:8.9441471099854左右

  • 第二种方法(迭代)耗时:6.7250330448151左右

  • 第三种方法(引用)耗时:0.028863906860352左右

我去,这差距,第三种方法真是逆天了。但是再次提醒,这只是一次性读取多的数据的时候,当数据量很小的时候,相差无几,不一定非要用最高效率的,还可以通过懒加载等其他方式来实现。

顺便封装个类,可以增加一些填充什么的。更多的细节可以查看下面的类:


  1 <?php  2 /**  3  * parent_id类型树结构相关  4  * 没必要非要写成静态的方法,静态方法参数太多,所以用实例在构造函数中修改参数更合适  5  * 需要首先将所有数据取出,然后再用此方法重新规划数组,其它的边取边查询数据库的方法不推荐  6  * 经测试第一种方法要快很多,建议使用  7  * @author   vishun <nadirvishun@gmail.com>  8  */  9  10 class Tree 11 { 12     /** 13      * 图标 14      */ 15     public $icon = '└'; 16     /** 17      * 填充 18      */ 19     public $blank = '   '; 20     /** 21      * 默认ID字段名称 22      */ 23     public $idName = 'id'; 24     /** 25      * 默认PID字段名称 26      */ 27     public $pidName = 'pid'; 28     /** 29      * 默认名称字段名称 30      */ 31     public $titleName = 'name'; 32     /** 33      * 默认子元素字段名称 34      */ 35     public $childrenName = 'items'; 36  37     /** 38      * 构造函数,可覆盖默认字段值 39      * @param array $config 40      */ 41     function construct($config = []) 42     { 43         if (!empty($config)) { 44             foreach ($config as $name => $value) { 45                 $this->$name = $value; 46             } 47         } 48     } 49  50     /** 51      * 生成下拉菜单可用树列表的方法 52      * 经测试4000条地区数据耗时0.02左右,比另外两种方法快超级多 53      * 流程是先通过引用方法来生成一种特殊树结构,再通过递归来解析这种特殊的结构 54      * @param array $list 55      * @param int $pid 56      * @param int $level 57      * @return array 58      */ 59     public function getTreeOptions($list, $pid = 0, $level = 0) 60     { 61         //先生成特殊规格的树 62         $tree = $this->getTree($list, $pid); 63         //再组装成select需要的形式 64         return $this->formatTree($tree, $level); 65     } 66  67     /** 68      * 通过递归来解析特殊的树结构来组装成下拉菜单所需要的样式 69      * @param array $tree 特殊规格的数组 70      * @param int $level 71      * @return array 72      */ 73     protected function formatTree($tree, $level = 0) 74     { 75         $options = []; 76         if (!empty($tree)) { 77             $blankStr = str_repeat($this->blank, $level) . $this->icon; 78             if ($level == 0) {//第一次无需有图标及空格 79                 $blankStr = ''; 80             } 81             foreach ($tree as $key => $value) { 82                 $options[$value[$this->idName]] = $blankStr . $value[$this->titleName]; 83                 if (isset($value[$this->childrenName])) {//查询是否有子节点 84                     $optionsTmp = $this->formatTree($value[$this->childrenName], $level + 1); 85                     if (!empty($optionsTmp)) { 86                         $options = array_merge($options, $optionsTmp); 87                     } 88                 } 89             } 90         } 91         return $options; 92     } 93  94     /** 95      * 生成类似下种格式的树结构 96      * 利用了引用&来实现,参照:http://blog.csdn.net/gxdvip/article/details/24434801 97      * [ 98      *  'id'=>1, 99      *  'pid'=>0,100      *  'items'=>[101      *      'id'=>2,102      *      'pid'=>'1'103      *       。。。104      *  ]105      * ]106      * @param array $list107      * @param int $pid108      * @return array109      */110     protected function getTree($list, $pid = 0)111     {112         $tree = [];113         if (!empty($list)) {114             //先修改为以id为下标的列表115             $newList = [];116             foreach ($list as $k => $v) {117                 $newList[$v[$this->idName]] = $v;118             }119             //然后开始组装成特殊格式120             foreach ($newList as $value) {121                 if ($pid == $value[$this->pidName]) {122                     $tree[] = &$newList[$value[$this->idName]];123                 } elseif (isset($newList[$value[$this->pidName]])) {124                     $newList[$value[$this->pidName]][$this->childrenName][] = &$newList[$value[$this->idName]];125                 }126             }127         }128         return $tree;129     }130 131     /**132      * 第二种方法,利用出入栈迭代来实现133      * 经测试4000条地区数据耗时6.5s左右,比较慢134      * @param $list135      * @param int $pid136      * @param int $level137      * @return array138      */139     public function getTreeOptions2($list, $pid = 0, $level = 0)140     {141         $tree = [];142         if (!empty($list)) {143 144             //先将数组反转,因为后期出栈时会有限出最上面的145             $list = array_reverse($list);146             //先取出顶级的来压入数组$stack中,并将在$list中的删除掉147             $stack = [];148             foreach ($list as $key => $value) {149                 if ($value[$this->pidName] == $pid) {150                     array_push($stack, ['data' => $value, 'level' => $level]);//将层级记录下来,方便填充空格151                     unset($list[$key]);152                 }153             }154             while (count($stack)) {155                 //先从栈中取出第一项156                 $info = array_pop($stack);157                 //查询剩余的$list中pid与其id相等的,也就是查找其子节点158                 foreach ($list as $key => $child) {159                     if ($child[$this->pidName] == $info['data'][$this->idName]) {160                         //如果有子节点则入栈,while循环中会继续查找子节点的下级161                         array_push($stack, ['data' => $child, 'level' => $info['level'] + 1]);162                         unset($list[$key]);163                     }164                 }165                 //组装成下拉菜单格式166                 $blankStr = str_repeat($this->blank, $info['level']) . $this->icon;167                 if ($info['level'] == 0) {//第一次无需有图标及空格168                     $blankStr = '';169                 }170                 $tree[$info['data'][$this->idName]] = $blankStr . $info['data'][$this->titleName];171             }172         }173         return $tree;174     }175 176     /**177      * 第三种普通列表转为下拉菜单可用的树列表178      * 经测试4000条地区数据耗时8.7s左右,最慢179      * @param array $list 原数组180      * @param int $pid 起始pid181      * @param int $level 起始层级182      * @return array183      */184     public function getTreeOptions3($list, $pid = 0, $level = 0)185     {186         $options = [];187         if (!empty($list)) {188             $blankStr = str_repeat($this->blank, $level) . $this->icon;189             if ($level == 0) {//第一次无需有图标及空格190                 $blankStr = '';191             }192             foreach ($list as $key => $value) {193                 if ($value[$this->pidName] == $pid) {194                     $options[$value[$this->idName]] = $blankStr . $value[$this->titleName];195                     unset($list[$key]);//销毁已查询的,减轻下次递归时查询数量196                     $optionsTmp = $this->getTreeOptions3($list, $value[$this->idName], $level + 1);//递归197                     if (!empty($optionsTmp)) {198                         $options = array_merge($options, $optionsTmp);199                     }200                 }201             }202         }203         return $options;204     }205 }


View Code

以上记录下,如转载请标明来源地址

 

以上是无限极分类的三种方式(迭代、递归、引用)的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
PHP和Python:解释了不同的范例PHP和Python:解释了不同的范例Apr 18, 2025 am 12:26 AM

PHP主要是过程式编程,但也支持面向对象编程(OOP);Python支持多种范式,包括OOP、函数式和过程式编程。PHP适合web开发,Python适用于多种应用,如数据分析和机器学习。

PHP和Python:深入了解他们的历史PHP和Python:深入了解他们的历史Apr 18, 2025 am 12:25 AM

PHP起源于1994年,由RasmusLerdorf开发,最初用于跟踪网站访问者,逐渐演变为服务器端脚本语言,广泛应用于网页开发。Python由GuidovanRossum于1980年代末开发,1991年首次发布,强调代码可读性和简洁性,适用于科学计算、数据分析等领域。

在PHP和Python之间进行选择:指南在PHP和Python之间进行选择:指南Apr 18, 2025 am 12:24 AM

PHP适合网页开发和快速原型开发,Python适用于数据科学和机器学习。1.PHP用于动态网页开发,语法简单,适合快速开发。2.Python语法简洁,适用于多领域,库生态系统强大。

PHP和框架:现代化语言PHP和框架:现代化语言Apr 18, 2025 am 12:14 AM

PHP在现代化进程中仍然重要,因为它支持大量网站和应用,并通过框架适应开发需求。1.PHP7提升了性能并引入了新功能。2.现代框架如Laravel、Symfony和CodeIgniter简化开发,提高代码质量。3.性能优化和最佳实践进一步提升应用效率。

PHP的影响:网络开发及以后PHP的影响:网络开发及以后Apr 18, 2025 am 12:10 AM

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP类型提示如何起作用,包括标量类型,返回类型,联合类型和无效类型?PHP类型提示如何起作用,包括标量类型,返回类型,联合类型和无效类型?Apr 17, 2025 am 12:25 AM

PHP类型提示提升代码质量和可读性。1)标量类型提示:自PHP7.0起,允许在函数参数中指定基本数据类型,如int、float等。2)返回类型提示:确保函数返回值类型的一致性。3)联合类型提示:自PHP8.0起,允许在函数参数或返回值中指定多个类型。4)可空类型提示:允许包含null值,处理可能返回空值的函数。

PHP如何处理对象克隆(克隆关键字)和__clone魔法方法?PHP如何处理对象克隆(克隆关键字)和__clone魔法方法?Apr 17, 2025 am 12:24 AM

PHP中使用clone关键字创建对象副本,并通过\_\_clone魔法方法定制克隆行为。1.使用clone关键字进行浅拷贝,克隆对象的属性但不克隆对象属性内的对象。2.通过\_\_clone方法可以深拷贝嵌套对象,避免浅拷贝问题。3.注意避免克隆中的循环引用和性能问题,优化克隆操作以提高效率。

PHP与Python:用例和应用程序PHP与Python:用例和应用程序Apr 17, 2025 am 12:23 AM

PHP适用于Web开发和内容管理系统,Python适合数据科学、机器学习和自动化脚本。1.PHP在构建快速、可扩展的网站和应用程序方面表现出色,常用于WordPress等CMS。2.Python在数据科学和机器学习领域表现卓越,拥有丰富的库如NumPy和TensorFlow。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前By尊渡假赌尊渡假赌尊渡假赌
威尔R.E.P.O.有交叉游戏吗?
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

螳螂BT

螳螂BT

Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。