(无限级、非递归)树形分类

(无限级、非递归)树形分类

     记得之前有一次去面试,被问了无限级怎么做。我想很简单,就说了最基本的结构:id、name、parentid。

又被问就这样吗?显然不被满意。后面自然就没通过面试。

     遇上技术型面试官,如果问的技术问题不被满意,大抵就没有下文了。遇上那些一副老子技术天下第一,狗眼看人低的面试官,那就自认倒霉吧。

     遇上追问你如何规划人生,比如五年规划什么的。笔者现在只堪堪在温饱线上挣扎,只想提高点技术实力,多拿点工资。如果笔者有大能耐,就完全可以和面试官侃侃而谈雄心壮志,也可以完全不甩面试官,甚至可以发出一个鄙夷的眼光。奈何笔者没什么能耐,不得不认真回答面试官,却又因为回答的空泛无力,还要被面试官指手画脚一番。结果自然又是没有下文。

     回去之后,在百度上翻一下非递归版的无限级。就是多了几个排序字段,在CRUD操作时,使每条保存在数据库的记录,按树形排序。避免了在获取数据时要通过递归组成树形结果的性能消耗。

     毕竟树形分类在网站系统中,查询是最频繁的操作,反而在增删改操作极少。所以在保存到数据库时,为了按树形排序,增加了增删改的复杂度,却可以换来查询上性能的提升。

     此方案的发明者无疑是充满智慧的。

     不过我想,我只是刚好在实际工作中没有用到它而已。

     没用过的技术或方案多了去。下次再遇上这种拿几个技术问题唧唧歪歪的面试官,哥决定了不甩他。

 

     本方案是在mysql+ci php mvc下设计的,所以命名习惯随俗。

     下面是表的设计结构

字段 说明
id 分类编号
parent_id 分类的父亲节点
name 分类名称
parent_path 父节点的路径,用于找到一个节点的子节点和子子节点或所有子节点。也可以找到一个节点的所有父节点,比如在删除节点时同时删除其下所有子节点
order_path 所有节点按树形排序,可以一个sql语句提取树形排序的分类,而不需要递归
level 第几级节点,可以和css配合,美化和层次化显示效果
order_id 辅助order_path完成同一级别下的排序

 

    添加数据

有两种情况:1.添加根结点 2.添加子节点

1.添加根结点

需要注意,parent_path=‘0,’ ,

             level=’0’  ,

             order_id,'select max(order_id) as mOrder_id from info_class where parent_id=0’ ,mOrder_id存在,则order_id=mOrder_id+1,否则order_id=1  ,

             order_path等于order_id前面补零,凑足四位。每一个级别4位的编码,可以存放0001~9999个分类,完全足够使用。

2.添加子节点

需要注意,parent_path=父节点的parent_path+父节点的id+‘,'   ,

             level,通过把parent_path转换为数组,得到数组长度来计算级别  ,

             order_id,'select max(order_id) as mOrder_id from info_class where parent_id=父节点’ ,mOrder_id存在,则order_id=mOrder_id+1,否则order_id=1

             order_path=父节点的order_path+order_id补零4位

代码如下:

复制代码
    function insert()
    {
        $data['name']=$this->input->post('info_class');
        $data['style']=$this->input->post('info_style');
        //if 'style' array is not empty, set it as string separate with ','.
        if($data['style'][0]!='') $data['style']=implode(',',$data['style']);
        $data['parent_id']=$this->input->post('info_parent');
        if($data['parent_id']=='0'){
            //if add a root node
            $data['parent_path']='0,';
            $this->level='0';
            
            $this->db->select_max('order_id');
            $this->db->where('parent_id',0);
            $query=$this->db->get('mini_info_class');
            $data['order_id']=$query->row()->order_id+1;
            $data['order_path']=substr(strval($data['order_id']+1000),1,3);
        }else{
            //if add a child node
            $query=$this->info_class_model->GetClass(array('id'=>$data['parent_id']));
            $data['order_path']=$query->order_path;
            $data['parent_path']=$query->parent_path.$data['parent_id'].',';
            $data['level']=count(explode(',',$data['parent_path']))-2;
            
            $this->db->select_max('order_id');
            $this->db->where('parent_id',$data['parent_id']);
            $query=$this->db->get('mini_info_class');
            $data['order_id']=$query->row()->order_id+1;
            $data['order_path']=$data['order_path'].substr(strval($data['order_id']+1000),1,3);
        }
        
        $continue_add=$this->input->post('continue_add');
        if($this->info_class_model->AddClass($data)){
            if($continue_add=='1'){
                $this->session->set_userdata(array('pre_url'=>'mini_admin/info_class_add'));
            }
            redirect('index.php/submit_success','refresh');
        }
    }
复制代码

 

     修改数据

有三种情况,1.根结点、或升级为根结点 2.不更改父节点 3.更改父节点

1.根结点、或升级为根结点

需要注意,parent_id=0  ,

             parent_path=’0,’  ,

             level=0  ,

             如果是升级为根节点:

             order_id,'select max(order_id) as mOrder_id from info_class where parent_id=0’ ,mOrder_id存在,则order_id=mOrder_id+1,否则order_id=1  ,

             order_path等于order_id前面补零,凑足四位。每一个级别4位的编码,可以存放0001~9999个分类,完全足够使用。

2.不更改父节点

需要注意,parent_path=父节点的parent_path+父节点的id+‘,'   ,

             level,通过把parent_path转换为数组,得到数组长度来计算级别  ,

3.更改父节点

需要注意,parent_path=父节点的parent_path+父节点的id+‘,'   ,

             level,通过把parent_path转换为数组,得到数组长度来计算级别  ,

             ///

             old_order_path=该节点的原order_path,如果该节点有子节点,修改成功后,将子节点的order_path中old_order_path部分替换为新生成的order_path

             order_id,'select max(order_id) as mOrder_id from info_class where parent_id=新父节点’ ,mOrder_id存在,则order_id=mOrder_id+1,否则order_id=1

             temp_order_path等于order_id前面补零,凑足四位。

             order_path=新父节点的order_path+temp_order_path

代码如下:

复制代码
    function update($id)
    {
        $data['id']=$id;
        $data['name']=$this->input->post('info_class');
        $style=$this->input->post('info_style');
        if($style[0]!='') $data['style']=implode(',',$style);
        $data['parent_id']=$this->input->post('info_parent');
        $order_path='';
        $myRecord=$this->info_class_model->GetClass(array('id'=>$id));
        $newParentRecord=$this->info_class_model->GetClass(array('id'=>$data['parent_id']));
        if($data['parent_id']=='0'){
            $data['parent_id']='0';
            $data['parent_path']='0,';
            $data['level']='0';
            //if it's a children node before
            if($myRecord->parent_id!='0')
            {
                $this->db->select_max('order_id');
                $this->db->where('parent_id',$data['parent_id']);
                $query=$this->db->get('mini_info_class');
                $data['order_id']=$query->row()->order_id+1;
                $order_path=$order_path.substr(strval($data['order_id']+1000),1,3);
                $data['order_path']=$order_path;
            }
        }else{
            $data['parent_path']=$newParentRecord->parent_path.$data['parent_id'].',';
            $data['level']=count(explode(',',$data['parent_path']))-2;
            
            //get this node's parent_id
            if($myRecord->parent_id!=$data['parent_id']){
            //if you change it's parent node, must update it's 'order_path' in synchronization.
                $old_order_path=$myRecord->order_path;
                $new_order_path=$newParentRecord->order_path;
                $order_path=$new_order_path;
                
                $this->db->select_max('order_id');
                $this->db->where('parent_id',$data['parent_id']);
                $query=$this->db->get('mini_info_class');
                $data['order_id']=$query->row()->order_id+1;
                $order_path=$order_path.substr(strval($data['order_id']+1000),1,3);
                $data['order_path']=$order_path;
                $new_order_path=$order_path;
            }
        }

        if($this->info_class_model->UpdateClass($data)){
            if(isset($new_order_path)){
            //if you change it's parent node, must update children's 'order_path' in synchronization.
                $data1=array('order_path'=>"replace('order_path','".$old_order_path."','".$new_order_path."')");
                $this->db->where('parent_id',$id);
                $this->db->update('mini_info_class',$data1);
            }
            redirect('index.php/submit_success','refresh');
        }
    }
复制代码


    上移下移、或插入

只能在同一级别内,进行上移下移、或插入
1.上移

假设待上移节点为currentNode

根据currentNode获取上一个节点,再根据上一个节点,获取上上一个节点就是待插入位置的节点,假设为brotherNode,然后就可以把currentNode上移到brotherNode之下了。

接下来,

获取currentNode的父节点的order_path,假设为father_order_path

设定currentNode的order_id=brotherNode的order_id+1

设定currentNode的order_path=father_order_path+currentNode的order_id补零四位

设定currentNode同一级别的其他节点,满足order_id大于brotherNode的order_id(不包括currentNode)条件的节点,order_id+1,order_path=father_order_path+order_id补零四位。

2.下移、插入

下移、插入都是在某一个节点下插入,区别是下移是在下一个节点下插入,而插入是选择在某一节点下插入

获取待下移节点为currentNode

获取待插入位置的节点为brotherNode

接下来,

获取currentNode父节点的order_path,假设为father_order_path

设定currentNode的order_id=brotherNode的order_id+1

设定currentNode的order_path=father_order_path+currentNode的order_id补零四位

设定currentNode同一级别的其他节点,满足order_id大于brotherNode的order_id(不包括currentNode)条件的节点,order_id+1,order_path=father_order_path+order_id补零四位。

上移和 下移、插入算法基本一致,只有获取待插入位置节点稍微有点不同。

 

     查询数据

select * from mini_info_class order by order_path

id parent_id parent_path name order_path order_id level
20 0 0, 天庭 002 2 0
27 20 0,20,    日游神 002001 1 1
6 0 0, 冥府 004 4 0
26 6 0,6,    鬼差 004002 2 1
11 0 0, 妖域 005 5 0
7 0 0, 魔界 006 6 0
12 0 0, 人间道 007 7 0
29 0 0, 修罗道 008 8 0
28 29 0,29,    阿修罗 008001 1 1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值