热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

无限分类数据格式化成树

 无限分类是web开发中经常遇到的,比如文章分类,商品分类,用户权限的分类等等。传统的做法是用递归的算法进行,而我们知道,递归即费时间,又费空间。那天下午,一哥们问无限分类,让我写个函数参考一下,当天晚上回去就开始,忙了四个多小时,终于写出来了,时间复杂度最小是n,最大是2n-(一级分类的类别数量),经过测试运效率的确比递归算法高&
无限分类是web开发中经常遇到的,比如文章分类,商品分类,用户权限的分类等等。传统的做法是用递归的算法进行,而我们知道,递归即费时间,又费空间。那天下午,一哥们问无限分类,让我写个函数参考一下,当天晚上回去就开始,忙了四个多小时,终于写出来了,时间复杂度最小是n,最大是 2n-(一级分类的类别数量),经过测试运效率的确比递归算法高

 14, 'pid' => 1, 'name' => '二级14' ),则 $son_keys[9] = "[1]['son'][5]['son'][9]",
	 *      则:$parent[1][14] = 14
	 */
	$parent = array();  //以键值作为父节点的子
	/**
	 * 定义树中的所有子孙节点所对应的键值:
	 *   如 $tree[1]['son'][5]['son'][9]= array('id' => 9, 'pid' => 1, 'name' => '二级13' )
	 *      则 $son_keys[9] = "[1]['son'][5]['son'][9]"
	 */
	$son_keys = array(); 
	foreach($items as $value){
		 //如果当前节点的父亲是一级(暂时是在一级)
	     if(isset($tree[$value['pid']])){
	     	$tree[$value['pid']]['son'][$value['id']] = $value;
	     	$son_keys[$value['id']] = "[{$value['pid']}]['son'][{$value['id']}]";
	     	//如果有以当前节点作为父节点
		    if(isset($parent[$value['id']])){
		     	foreach($parent[$value['id']] as $val){
		     		$tree[$value['pid']]['son'][$value['id']]["son"][$val]=$tree[$val];
		     	    unset($tree[$val]);
		     	    $son_keys[$val] = "[{$value['pid']}]['son'][{$value['id']}]['son'][$val]";
		     	}
		     	unset($parent[$value['id']]);
		     }
	     }elseif(isset($son_keys[$value['pid']])){ //如果当前节点的父亲类是二级或以上
	     	 $current_key = "{$son_keys[$value['pid']]}['son'][{$value['id']}]";
	     	 $son_keys[$value['id']] = $current_key; 
	     	 eval('$tree'.$current_key.'=$value;');
	     	 //如果有以当前节点作为父节点
		     if(isset($parent[$value['id']])){
		     	foreach($parent[$value['id']] as $val){
		     		eval('$tree'.$current_key.'["son"][$val]=$tree[$val];');
		     	    unset($tree[$val]);
		     	    $son_keys[$val] = "{$current_key}['son'][$val]";
		     	}
		     	unset($parent[$value['id']]);
		     }
	     }else{ //当前节点或暂时没有父亲
	     	 $tree[$value['id']] = $value;
	     	 //如果有以当前节点作为父节点
		     if(isset($parent[$value['id']])){
		     	 foreach($parent[$value['id']] as $val){
		     		 $tree[$value['id']]["son"][$val]=$tree[$val];
		     	     unset($tree[$val]);
		     	     $son_keys[$val] = "[{$value['id']}]['son'][$val]";
		     	 }
		     	 unset($parent[$value['id']]);
		      }
	     }
	     $parent[$value['pid']][$value['id']] = $value['id'];
	}
	return $tree;
}

/**
 * 用于格式化数组,仅用于调试
 * @author Xuefen.Tong
 * @date 2012-06-17
 */
function p_print_r($ar)
{
	echo '

'; echo "

";
	     print_r($ar);
		 echo "
"; echo "

"; } /** * 以下这个类是从Ecamll中copy过来的 * 我这里用于和我的代码进行性能的对比 * * 树 0是根结点 */ class Tree { var $data = array(); var $child = array(-1 => array());//父亲节点与孩子节点的关系映射 var $layer = array(0 => 0);//初始节点为0 var $parent = array();//非叶子节点的节点,也就是有孩子节点的节点 var $value_field = '';//一般是分类名称 var $id_field = '';//子节点的名称 var $parent_field = '';//父节点的名称 /** * 构造函数 * * @param mix $value */ function __construct($value = 'root') { $this->setNode(0, -1, $value); } /** * 构造树 * * @param array $nodes 结点数组 * @param string $id_field * @param string $parent_field * @param string $value_field */ function setTree($nodes, $id_field, $parent_field, $value_field) { $this->value_field = $value_field; $this->id_field =$id_field; $this->parent_field = $parent_field; foreach ($nodes as $node) { $this->setNode($node[$this->id_field], $node[$this->parent_field ], $node); } $this->setLayer(); } /** * 取得options * * @param int $layer * @param int $root * @param string $space * @return array (id=>value) */ function getOptions($layer = 0, $root = 0, $except = NULL, $space = ' ') { $optiOns= array(); $childs = $this->getChilds($root, $except); foreach ($childs as $id) { if ($id > 0 && ($layer <= 0 || $this->getLayer($id) <= $layer)) { $options[$id] = $this->getLayer($id, $space) . htmlspecialchars($this->getValue($id)); } } return $options; } /** * 设置结点 * * @param mix $id * @param mix $parent * @param mix $value */ function setNode($id, $parent, $value) { $parent = $parent ? $parent : 0; $this->data[$id] = $value; if (!isset($this->child[$id])) { $this->child[$id] = array(); } if (isset($this->child[$parent])) { $this->child[$parent][] = $id; } else { $this->child[$parent] = array($id); } $this->parent[$id] = $parent; } /** * 计算layer */ function setLayer($root = 0) { foreach ($this->child[$root] as $id) { $this->layer[$id] = $this->layer[$this->parent[$id]] + 1; if ($this->child[$id]) $this->setLayer($id); } } /** * 先根遍历,不包括root * * @param array $tree * @param mix $root * @param mix $except 除外的结点,用于编辑结点时,上级不能选择自身及子结点 */ function getList(&$tree, $root = 0, $except = NULL) { foreach ($this->child[$root] as $id) { if ($id == $except) { continue; } $tree[] = $id; if ($this->child[$id]) $this->getList($tree, $id, $except); } } function getValue($id) { return $this->data[$id][$this->value_field]; } function getLayer($id, $space = false) { return $space ? str_repeat($space, $this->layer[$id]) : $this->layer[$id]; } function getParent($id) { return $this->parent[$id]; } /** * 取得祖先,不包括自身 * * @param mix $id * @return array */ function getParents($id) { while ($this->parent[$id] != -1) { $id = $parent[$this->layer[$id]] = $this->parent[$id]; } ksort($parent); reset($parent); return $parent; } function getChild($id) { return $this->child[$id]; } /** * 取得子孙,包括自身,先根遍历 * * @param int $id * @return array */ function getChilds($id = 0, $except = NULL) { $child = array($id); $this->getList($child, $id, $except); unset($child[0]); return $child; } /** * 先根遍历,数组格式 id,子id,value对应的值 * array( * array('id' => '', 'value' => '', children => array( * array('id' => '', 'value' => '', children => array()), * )) * ) */ function getArrayList($root = 0 , $layer = NULL) { $data = array(); foreach ($this->child[$root] as $id) { if($layer && $this->layer[$this->parent[$id]] > $layer-1) { continue; } $data[] = array($this->id_field => $id,$this->value_field=> $this->getValue($id), 'children' => $this->child[$id] ? $this->getArrayList($id , $layer) : array()); // $data[] = array('id' => $id, 'value' => $this->getValue($id), 'children' => $this->child[$id] ? $this->getArrayList($id , $layer) : array()); } return $data; } } #----------------------------------------------------------------------- #-------这组数据的特点是父类的数据在前面-------------------------------- #-------如果算法从父类开始,循环次数少,次数为18,正好时间复杂度正好是n- #-------如果是其他数据,肯定小于这个数值-------------------------------- #----------------------------------------------------------------------- $items1 = array( array('id' => 1, 'pid' => 0, 'name' => '一级11' ), array('id' => 11, 'pid' => 0, 'name' => '一级12' ), array('id' => 2, 'pid' => 1, 'name' => '二级21' ), array('id' => 10, 'pid' => 11, 'name' => '二级22' ), array('id' => 3, 'pid' => 1, 'name' => '二级23' ), array('id' => 12, 'pid' => 11, 'name' => '二级24' ), array('id' => 9, 'pid' => 1, 'name' => '二级25' ), array('id' => 14, 'pid' => 1, 'name' => '二级26' ), array('id' => 4, 'pid' => 9, 'name' => '三级31' ), array('id' => 6, 'pid' => 9, 'name' => '三级32' ), array('id' => 7, 'pid' => 4, 'name' => '四级41' ), array('id' => 8, 'pid' => 4, 'name' => '四级42' ), array('id' => 5, 'pid' => 4, 'name' => '四级43' ), array('id' => 13, 'pid' => 4, 'name' => '四级44' ), array('id' => 15, 'pid' => 8, 'name' => '五级51' ), array('id' => 16, 'pid' => 8, 'name' => '五级52' ), array('id' => 17, 'pid' => 8, 'name' => '五级53' ), array('id' => 18, 'pid' => 16, 'name' => '六级64' ), ); #----------------------------------------------------------------------- #-------这组数据的特点是父类的数据在后面-------------------------------- #-------在我写的函数里面,这级数据的循环次数最多,为34次,-------------- #-------如果是其他数据,肯定小于这个数值-------------------------------- #----------------------------------------------------------------------- $items2 = array( array('id' => 18, 'pid' => 16, 'name' => '六级64' ), array('id' => 15, 'pid' => 8, 'name' => '五级51' ), array('id' => 16, 'pid' => 8, 'name' => '五级52' ), array('id' => 17, 'pid' => 8, 'name' => '五级53' ), array('id' => 5, 'pid' => 4, 'name' => '四级43' ), array('id' => 7, 'pid' => 4, 'name' => '四级41' ), array('id' => 13, 'pid' => 4, 'name' => '四级44' ), array('id' => 8, 'pid' => 4, 'name' => '四级42' ), array('id' => 6, 'pid' => 9, 'name' => '三级32' ), array('id' => 4, 'pid' => 9, 'name' => '三级31' ), array('id' => 2, 'pid' => 1, 'name' => '二级21' ), array('id' => 10, 'pid' => 11, 'name' => '二级22' ), array('id' => 3, 'pid' => 1, 'name' => '二级23' ), array('id' => 12, 'pid' => 11, 'name' => '二级24' ), array('id' => 9, 'pid' => 1, 'name' => '二级25' ), array('id' => 14, 'pid' => 1, 'name' => '二级26' ), array('id' => 1, 'pid' => 0, 'name' => '一级11' ), array('id' => 11, 'pid' => 0, 'name' => '一级12' ), ); //两种不同顺序的数据的循环次数对比 $time1 = microtime(true); $tree1 = genTree($items1); $time2 = microtime(true); $tree2 = genTree($items2); $time3 = microtime(true); //采用ecmall中tree类格式化数据算法 $tree = new Tree(); $tree->setTree($items1, 'id', 'pid', 'name'); $Ctree1 = $tree->getArrayList(); $time4 = microtime(true); $tree = new Tree(); $tree->setTree($items2, 'id', 'pid', 'name'); $Ctree2 = $tree->getArrayList(); $time5 = microtime(true); echo "function 时间1:",($time2-$time1),", 时间2:",($time3-$time2),"
"; echo "tree   类时间1:",($time4-$time3),", 时间2:",($time5-$time4),"
"; p_print_r($tree1); p_print_r($tree2); p_print_r($Ctree2); p_print_r($Ctree1);


推荐阅读
  • Monkey《大话移动——Android与iOS应用测试指南》的预购信息发布啦!
    Monkey《大话移动——Android与iOS应用测试指南》的预购信息已经发布,可以在京东和当当网进行预购。感谢几位大牛给出的书评,并呼吁大家的支持。明天京东的链接也将发布。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 橱窗设计的表现手法及其应用
    本文介绍了橱窗设计的表现手法,包括直接展示、寓意与联想、夸张与幽默等。通过对商品的折、拉、叠、挂、堆等陈列技巧,橱窗设计能够充分展现商品的形态、质地、色彩、样式等特性。同时,寓意与联想可以通过象形形式或抽象几何道具来唤起消费者的联想与共鸣,创造出强烈的时代气息和视觉空间。合理的夸张和贴切的幽默能够明显夸大商品的美的因素,给人以新颖奇特的心理感受,引起人们的笑声和思考。通过这些表现手法,橱窗设计能够有效地传达商品的个性内涵,吸引消费者的注意力。 ... [详细]
  • faceu激萌变老特效的使用方法详解
    本文介绍了faceu激萌变老特效的使用方法,包括打开faceu激萌app、点击贴纸、选择热门贴纸中的变老特效,然后对准人脸进行拍摄,即可给照片添加变老特效。操作简单,适合新用户使用。 ... [详细]
  • 大连微软技术社区举办《.net core始于足下》活动,获得微软赛百味和易迪斯的赞助
    九月十五日,大连微软技术社区举办了《.net core始于足下》活动,共有51人报名参加,实际到场人数为43人,还有一位专程从北京赶来的同学。活动得到了微软赛百味和易迪斯的赞助,场地也由易迪斯提供。活动中大家积极交流,取得了非常成功的效果。 ... [详细]
  • 给定一个二叉树,要求随机选择树上的一个节点。解法:遍历树的过程中,随机选择一个节点即可。具体做法参看:从输入 ... [详细]
  • 本文介绍了在微店中如何修改分销产品的价格以及设置价格的方法。客户在拍下商品后,在1小时内可以进行修改价格的操作,通过进入订单管理,点击未付款子项,可以找到订单信息并进行改价操作。修改价格后,买家会收到改价后的短信通知,在微店订单中进行付款即可。 ... [详细]
author-avatar
ACE纞_814
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有