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

     记得之前有一次去面试,被问了无限级怎么做。我想很简单,就说了最基本的结构: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

idparent_idparent_pathnameorder_pathorder_idlevel
2000,天庭00220
27200,20,   日游神00200111
600,冥府00440
2660,6,   鬼差00400221
1100,妖域00550
700,魔界00660
1200,人间道00770
2900,修罗道00880
28290,29,   阿修罗00800111
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值