一般的分类树状结构有两种方式:
一种是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中文网其他相关文章!

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

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

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

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

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

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

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

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


热AI工具

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

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

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

VSCode Windows 64位 下载
微软推出的免费、功能强大的一款IDE编辑器

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

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

Dreamweaver Mac版
视觉化网页开发工具

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