您的位置:首页 > 其它

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

2014-03-28 15:51 423 查看


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

记得之前有一次去面试,被问了无限级怎么做。我想很简单,就说了最基本的结构: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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: