无限极分类递归,引用,栈三种方法的实现,并根据分类id倒回去查找所有所属父级分类信息

2 篇文章 0 订阅

最近做的一个项目涉及到无限分类的问题,现在总结如下,代码使用PHP语言实现,因为使用了TP3的框架,所以一些方法的使用上,直接使用了TP3现有的方法。

想要根据分类id查找所有所属父级分类信息请往后拉,这部分在最后实现!!!

建表:

涉及无限极分类的问题建表时,都会有一个专门的分类表,表中有一个pid字段默认为0,属于一级分类,pid字段关联当前分类表的id,用来标明当前分类所属的上级分类,例如建一个问题分类表的语句如下:

CREATE TABLE `quescate` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '问题分类id',
  `title` varchar(250) NOT NULL COMMENT '问题分类标题',
  `pid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所属问题id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='问题分类表';

一、无限极分类的实现

接下来取出各级分类及分类下的子分类,自己做时用了最笨的办法:

(1)先查找一级分类(pid=0)

(2)通过上级分类查找的id作为pid,来查找下一级分类

(3)重复步骤(2)

代码实现如下:

<?PHP 
    $cate = array();
    // 一级分类
    $cate_0 = $this->where('pid = 0')->select();
    if (!empty($cate_0)){
        // 二级分类
        $ids_1 = array_map('reset', $cate_0); // 使用array_column()更简单,但是需要>=PHP5.5.0的版本
        $map_1['pid'] = array('in', $ids_1);
        $cate_1 = $this->where($map_1)->select();
        if(!empty($cate_1)){
            // 三级分类
            $ids_2 = array_map('reset', $cate_1);
            $map_2['pid'] = array('in', $ids_2);
            $cate_2 = $this->where($map_2)->select();
        }
        $cate = array(0=>$cate_0, 1=>$cate_1, 2=>$cate_2);
    }
    unset($ids_1, $map_1, $ids_2, $map_2);

    return $cate;
?>

但是这样的办法,有几级分类就要查找几次,太过麻烦,也会造成代码冗余。根据上述步骤很轻易看出,这其实就是一个递归。于是在优化代码的过程中,可以通过递归实现无限极分类,通过查找资料还找到了引用,栈的实现方法,下面一一介绍,为了更直观,使用的数据如下:

<?php 
    $res = array(
        array('id' => 8, 'title' => '美国', 'pid' => 0),
        array('id' => 9, 'title' => '纽约', 'pid' => 8),
        array('id' => 10, 'title' => '韩国', 'pid' => 0),
        array('id' => 11, 'title' => '日本', 'pid' => 0),
        array('id' => 12, 'title' => '加拿大', 'pid' => 0),
        array('id' => 13, 'title' => '中国', 'pid' => 0),
        array('id' => 14, 'title' => '华盛顿', 'pid' => 8),
        array('id' => 16, 'title' => '洛杉矶', 'pid' => 8),
        array('id' => 17, 'title' => '北京', 'pid' => 13),
        array('id' => 18, 'title' => '上海', 'pid' => 13),
        array('id' => 19, 'title' => '广东', 'pid' => 13),
        array('id' => 20, 'title' => '深圳', 'pid' => 13),
        array('id' => 21, 'title' => '杭州', 'pid' => 13)
    );
?>
1. 递归,实现无限极分类。递归应该是最容易想到的方法了。其实递归函数也是借助于栈的机制实现的,但是底层对于栈的处理对于程序员来说都是透明的,程序员只需要关心应用的实现逻辑。所以说使用递归处理上述问题理解起来比较容易,代码也比较简洁。在我看来,最容易理解的方法应该是使用栈的方式实现无限极分类了(可以参考以下的第三种方法)
主要参考博客:
php实现无限级分类查询(递归、非递归)中开头部分递归与栈的讲解
PHP实现无限极分类的两种方式,递归和引用中的递归方法
<?php 
    /**
     * 递归实现无限极分类
     * @param array $array 分类数据
     * @param Int $pid 父ID
     * @param Int $level 分类级别
     * @return Array $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
                $this->getTree($array, $value['id'], $level+1);
            }
        }
        return $list;
    }

    // 无限极分类的第一种方法:递归
    $list = getTree($res, 0, 1);
    /*
    * 以下是根据自己需要对分好类的数据进行的处理,这里为了保持与上边需要的数据形式一致
    */
    foreach ($list as $key=>$value){
        if ($value['level'] == 1){
            $cate[0][] = $value; // 一级分类
        }elseif ($value['level'] == 2){
            $cate[1][] = $value; // 二级分类
        }
    }

    return $cate;
?>

2. 引用,实现无限极分类。此方法非常巧用引用。如果不了解引用,对下面方法的理解是困难的,建议参考PHP手册引用一章

主要参考博客:PHP无限极分类实现中的方法一,里面的方法二只是判断将三目运算符改成了if判断,喜欢的可以看看

<?php 
    /**
     * 将数据格式转换成树形结构数组
     * @param array $items 要进行转换的数组
     * @return array $items 转换完成的数组
     */
    function arrayToTree(Array $items) {
        foreach ($items as $key=>$item) {
            $items[$item['pid']]['son'][$key] = &$items[$key];
        }
        return isset($items[0]['son']) ? $items[0]['son'] : array();
    }

    $cate[1] = array(); // 预定义二级分类为空
    // 将数组索引改为id值
    $res = array_reduce($res, create_function('$res, $val', '$res[$val["id"]] = $val; return $res;'));
    $list = $this->arrayToTree($res);
    /*
    * 以下是根据自己需要对分好类的数据进行的处理,这里为了保持与上边需要的数据形式一致
    */
    foreach ($list as $ke=>$val){
        if (isset($val['son'])){
            $cate[1] += $val['son']; // 二级菜单
            unset($val['son']); // 从二级菜单中去除一级菜单
        }
        $cate[0][] = $val; // 一级菜单
    }

    return $cate;
?>

3. 栈,实现无限极分类。栈的实现方式应该算是最容易理解的了,当然栈的核心:“先进后出”是必须要知道才能理解好下面的方法的。有关于栈,有兴趣的可以查一查《数据结构(C语言版)》中对于栈的讲解。

主要参考博客:php实现无限级分类查询(递归、非递归)中的“非递归,即使用栈机制实现无限级栏目的查询”

<?php 
    /**
     * 非递归,即使用栈机制实现无限级栏目的查询
     * 其实栈的核心机制也就四个字:先进后出。
     * 在这对于栈的机制不多说,主要说一下如何借助栈实现无限级栏目查询:
     * 1. 首先将顶级栏目压入栈中
     * 2. 将栈顶元素出栈
     * 3. 将出栈元素存入数组中,标记其深度(其深度就是在其父栏目的深度上面加1)
     * 4. 以出栈的元素为父栏目,查找其子栏目
     * 5. 将查找到的子栏目入栈,重复步骤2
     * 6. 判断栈为空的话,流程结束;
     * @param Array $array 要进行栈操作的数组
     * @return Array $list 分好类的数组
    */
    // 自定义入栈函数
    function pushStack(&$stack, $channel, $dep)
    {
        $channel['dep'] = $dep;
        array_push($stack, $channel);
    }
    // 自定义出栈函数
    function popStack(&$stack)
    {
        return array_pop($stack);
    }
    function getStack($array)
    {
        $stack = array(); //定义一个空栈
        $list = array();  //用来保存各个栏目之间的关系以及该栏目的深度
        /*
         * 首先将顶级栏目压入栈中
         * */
        foreach ($array as $key => $val) {
            if ($val['pid'] == 0)
                pushStack($stack, $val, 1);
        }
        /*
         * 将栈中的元素出栈,查找其子栏目
         * */
        do {
            $par = popStack($stack); //将栈顶元素出栈
            /*
             * 查找以此栏目为父级栏目的id,将这些栏目入栈
             * */
            foreach ($array as $key=>$value){
                if ($value['pid'] == $par['id']){
                    pushStack($stack, $value, $par['dep']+1);
                }
            }
            // 将出栈的栏目以及该栏目的深度保存到数组中
            $list[] = $par;
        } while (count($stack) > 0);

        return $list;
    }

    //$res = $this->order('id desc')->select(); // 因为栈先进后出的特点,分类完后的数据会是原数据的倒序排序。这可以在处理数据前,将数据先倒序输出,这样得到的数据就是正序的。
    $res = array_reverse($res);
    $list = getStack($res);
    foreach ($list as $key=>$value){
        if ($value['dep'] == 1){
            $cate[0][] = $value; // 一级分类
        }elseif ($value['dep'] == 2){
            $cate[1][] = $value; // 二级分类
        }
    }

    return $cate;
?>

总结:通过以上三种方法可以发现:递归会是最容易想到也最常用到的一种方式,比较容易理解使用方便;引用使用非常巧妙而且代码简洁但是不太好理解;栈的实现最容易理解,但要理解并注意栈的特点:先进后出。

二、根据分类id倒着查找所有的所属父分类信息

本来想将这部分放在下一篇博客讲解的,但想趁热打铁,索性放在一篇博客里总结完。

项目中有时候需要根据分类的id倒回去查找所有所属的父级分类信息,比如面包屑,前端菜单栏里只允许展示有具体信息的分类时,我们就要涉及到这个查找。同样的这个查找也需要用到递归的实现。下面是两种递归的实现方法,但大同小异,基本原理都一样。

主要参考博文:php实现无限极分类中的“根据子类id查找出所有父级分类信息”

1. 方法一:Yii2框架中的方法

<?php 
    /**
     * Yii2框架中,根据子类id查找所有父级分类的方法
     * @param  array $arr 所有分类列表
     * @param  Int  $id 父级分类id
     * @return array $list 所有父级分类信息
    */
    function get_parent_list($arr,$id){
        static $list=array();
        foreach($arr as $u){

            if($u['id']== $id){//父级分类id等于所查找的id
                $list[]=$u;

                if($u['pid']>0){
                    get_parent_list($arr,$u['pid']);

                }
            }
        }
        return $list;
    }

    $cate = get_parent_list($res, $pid); // 此处$pid根据查到的具体分类信息中的pid传值查询所属上级分类
    $cate = array_reverse($cate); // 此处根据自己需要做处理,我这里为了页面展示方便做了数组倒序处理
?>

2. 方法二:递归查询分类信息,与方法一大同小异

<?php 
    function get_parents($id){
        static $list = [];
        $cateModel = new QuescateModel();
        $cate = $cateModel->where('id = '.$id)->find(); // 此处就是查询分类表中一条语句

        if($cate){
            $list[] = $cate;
            $id = $cate['pid'];
            if($cate['pid'] > 0){
                self::get_parents($id);
            }
        }

        return $list;
    }

    $cate = get_parents($pid); // 根据需要查要查询分级分类的分类id
    $cate = array_reverse($cate); // 此处我是根据自己前端展示分方便最数组做了倒序处理
?>

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值