无限级分类(php+mysql实现)

一、邻接表模型

邻接表模型中,数据表中的每项包含了指向其父项的指示器,最上层项的父项为0
建立表结构:

CREATE TABLE `category`(
    `id` int not null auto_increment primary key comment '类目ID',
    `name` varchar(30) not null comment '类目名',
    `pid` int not null default 0 comment '父ID'
)ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='分类表';

特点

  • 通过这种数据库设计出的无限级,读取的时候相当麻烦。
  • 所以大部分的程序最多3到4级分类,这就足以满足需求,从而一次性读出所有的数据,再对得到数组或者对象进行递归。
  • 本身负荷还是没太大问题。但是如果分类到更多级,那是不可取的办法。这样看来这种方法就是增删改的时候轻松了。然而就二级分类而言,采用这种算法就应该算最优先了。

PHP处理
递归方式

/**
* 数据集组合分类树(一维数组)
* @param     cate 分类查询结果集
* @param     html 格式串
* @return    array
*/
function list_to_tree($cate,$html = '--',$pid = 0,$level = 0){
    $arr = array();
    foreach($cate as $v){
        if($pid == $v['pid']){
            $v['level'] = $level + 1;
            $v['html']  = str_repeat($html,$level);
            $arr[]      = $v;
            $arr        = array_merge($arr,list_to_tree($cate,$html,$v['id'],$level+1));
        }
    }
    return $arr;
}

/**
* 数据集组合分类树(多维数组)
* @param     cate 分类结果集
* @param     child 子树
* @return    array
*/
function list_to_tree2($cate,$child = 'child',$pid = 0){
    $arr = array();
    foreach($cate as $v){
        if($v['pid'] == $pid){
            $v[$child] = list_to_tree2($cate,$child,$v['id']);
            $arr[]     = $v;
        }
    }
    return $arr;
}

/**
* 通过子级ID返回父级
* @param     cate 分类结果集
*/
function get_parents_by_id($cate,$id){
    $arr = array();
    foreach($cate as $v){
        if($v['id'] == $id){
            $arr[] = $v;
            $arr = array_merge(get_parents_by_id($cate,$v['pid']),$arr);
        }
    }
    return $arr;
}

/**
* 通过父级ID返回子级
* @param     cate 分类结果集
*/
function get_childs_by_pid($cate,$pid){
    $arr = array();
    foreach($cate as $v){
        if($v['pid'] == $pid){
            $arr[] = $v;
            $arr = array_merge($arr,get_childs_by_pid($cate,$v['id']));
        }
    }
    return $arr;
}

非递归方式

//栈
function list_2_tree($list, $pid = 0)
    {
        $tree = [];

        if(!is_array($list) || empty($list)){
            return false;
        }

        //先将数组反转,因为出栈时会优先出最上面的
        $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);

            //查找子节点
            foreach ($list as $key => $child) {
                //如果有子节点则入栈,用以后续继续查找子节点的下级
                if ($child['pid'] == $info['id']) {
                    array_push($stack,  $child);
                    unset($list[$key]);
                }
            }

            //组装成下拉菜单格式
            $tree[$info['id']] = $info['name'];
        }

        return $tree;
    }

//引用
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;
}
function formatTree($tree)
{
    $options = [];
    if (!empty($tree)) {
        foreach ($tree as $key => $value) {
            $options[$value['id']] = $value['name'];
            if (isset($value['items'])) {
                //查询是否有子节点
                $optionsTmp = formatTree($value['items']);
                if (!empty($optionsTmp)) {
                    $options = array_merge($options, $optionsTmp);
                }
            }
        }
    }
    return $options;
}
//先调用getTree,再调用formatTree,formatTree虽然是递归,但是通过了getTree的特殊处理,效率有很大提高。

注意
在删除节点的时候要特别小心,因为潜在的可能会孤立一棵子树

二、预排序遍历树

timg.jpg

为树状的结构编号时,从左到右,一次一层,为节点赋右值前先从左到右遍历其子节点给其子节点赋左右值。这种方法被称作改进的先序遍历算法或者左右值

  • 每一个 后代节点 的 左值 > 父节点 的 左值
  • 每一个 后代节点 的 右值 < 父节点的 右值
  • 每一个节点的 右值 > 左值

改进的先序遍历算法便于输出和查询,但是在移动分类和常规理解上有些复杂。
建立表结构:

CREATE TABLE `category`(
    `id` int not null auto_increment primary key comment '分类ID',
    `name` varchar(30) not null comment '类目名',
    `lft` int not null comment '左值',
    `rgt` int not null comment '右值'
)ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='分类表';

查询

检索所有叶子节点(叶子节点的左右值是连续的,要检索出叶子节点,我们只要查找满足 rgt = lft+1 的节点。)
SELECT id,name FROM category WHERE rgt = lft + 1;

检索单一路径(以node节点为例)
SELECT id,name FROM category WHERE lft BETWEEN node.lft AND node.rgt;

检索节点深度(以node节点为例)
SELECT (COUNT(1) - 1) as level FROM category WHERE lft BETWEEN node.lft AND node.rgt;

检索整个树深度
SELECT node.id, CONCAT( REPEAT('----', COUNT(parent.label) - 1), node.label) AS label
FROM category AS node,category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.id
ORDER BY node.lft;
****结果****
一级分类
----二级分类
--------三级分类
--------三级分类
一级分类
----二级分类
--------三级分类

查询所有子节点(以node节点为例)
SELECT id,name FROM category WHERE lft > node.lft AND rgt < node.rgt;

新增节点

  • 新增顶级分类:

    1. 获取该树中 最大右值
    2. 左值 = 最大右值 + 1
    3. 右值 = 最大右值 + 2
  • 新增子节点:

    1. 首先获取父节点右值
UPDATE `catagory` SET `lft` = `lft`+2 WHERE `lft`>  `父节点右值`
UPDATE `catagory` SET `rgt` = `rgt` + 2 WHERE `rgt`>= `父节点右值`
  1. 插入新节点:新增节点左值 = 父节点右值 新增节点右值 = 新增节点左值 + 1

删除节点

  1. 获取删除节点的左右值 node.lft, node.rgt
  2. 删除该节点以及所有后代节点
DELETE FROM `catagory` WHERE `lft`>= node.lft AND `rgt`<= node.rgt;
  1. 更新左右值
$Value= node.rgt - node.lft + 1;
UPDATE `catagory` SET `lft`=`lft`- $Value WHERE `lft`> node.lft;
UPDATE `catagory` SET `rgt`=`rgt`- $Value WHERE `rgt`> node.rgt;

数据集组合分类树(多维数组)

function get_all_tree(){
        $sql        = "select `id`,`name`, `lft`, `rgt`, 1 as `level` from `category` where uniacid = '{$uniacid}' order by `lft` asc";
        $data       = pdo_fetchall($sql);
        if (!$data) {
            return false;
        }

        $P[1] = -1;//记录某层节点对应的父节点
        $prenode = '';//上一个节点
        foreach ($data as $key => &$v) {
            if ($key == 0) {
                $data[$P[$v['level']]]['children'][] = &$v;
                continue;
            }
            $prenode = $data[$key - 1];

            # 如果连续的两个节点 c 和 p 的左值增加幅度为 1,则说明其深度增加了 1
            # 且 p 为 c 及 c 的兄弟节点的父节点
            if ($v['lft'] == $prenode['lft'] + 1) {
                $v['level'] = $prenode['level'] + 1;
                if ($prenode['level'] > count($P)) {
                    $P[count($P) + 1] = $key - 1;
                }else{
                    $P[$v['level']] = $key - 1;
                }
            }elseif ($v['lft'] > $data[$P[$prenode['level']]]['rgt']) {
                # 如果 c 的左值大于 p 的父节点的右值,则说明 p 与 c 并非兄弟节点
                # 并可以计算出两节点的深度差为 c 的左值与 p 的右值之差减 1
                $v['level'] = $prenode['level'] - ($v['lft'] - $prenode['rgt'] - 1);
            }else{
                # 不满足以上两个条件,说明 c 是 p 的兄弟节点
                $v['level'] = $prenode['level'];
            }

            $data[$P[$v['level']]]['children'][] = &$v;
        }
        return $data[-1]['children'];
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容