版权声明:本文为博主原创文章,转载、引用,请署名作者注明文章出处
前言:因工作原因需要设计一个树形结构表,该树有如下特点:
1、无唯一根节点
2、每个节点只有1个父节点
3、每个节点可以有多个子节点
4、深度不限
而且在业务需求上,对增删查都有一定的要求,百度了N久,主要就找到这两种:
1、一般树(即parentid标记那种)
http://blog.csdn.net/heyangyi_19940703/article/details/51456064
2、预设左右值树(这里简称左右值树)
http://blog.csdn.net/monkey_d_meng/article/details/6647488
但在了解后发现一般树在增删方面效率高,但在查询方面差,而左右值树则相反,这两种都不适合当前的业务,于是就有以下的设计
一、结构描述
该设计主要参考左右值树,是在它的基础上作进一步处理而来,所以我叫它分级预设树,简称分级树
该树有以下特征:
1、name(上面的a、b、c)节点标识,可以作外键
2、value为该节点在某级的位置标记
3、min和max保存其子节点在其层级的最小位置和最大位置
4、节点的min值<=max值,无子节点时min=max=0,仅有一个子节点时min=max=子节点的value值
5、子孙节点的value值总是在其有子的父级的最小min与最大max之间
6、增加节点时,设新增节点的lv为N(N>1),则影响的深度为2,影响的层数分别为lv=N和lv=N-1层,lv=N层的影响广度为N层的maxvalue-newvalue,Lv=N-1的影响广度为N-1层的maxvalue-新增节点的父value
7、删除节点时,设删除节点的lv为N(N>1),最小的子孙的lv为M(M>N),则影响的深度为M-N+2层,影响广度为{(N~M)层(每层的maxvalue-删除节点minvalue+1)+1}
根据以上设计,下面进行树形CRUD算法推演
注:6、7特征是在使用过程发现的,可能会有不正确
二、树形CRUD算法
(1)获取某节点的子孙节点
(一)、算法
由上面的特征5可以得出查询算法为:下一层value值在有子的父节点的value最小节点的min值与最大value节点的max值,然后再迭代至下一层,直到min=max=0;
代码如下:
declare @id int ---传入的节点id或者使用其他唯一标识都可以
set @id=1 declare @lv int,@min int,@max int,@result int
select @lv=lv,@min=[min],@max=[max] from testtree where id=@id
set @result=0
while(@min<>0) begin set @lv +=1 select * from testtree where lv =@lv and value >=@min and value <=@max ---查找子节点
set @result=@result+@max-@min +1 select @min= min([min]),@max=max([max]) from testtree where lv =@lv and value >=@min and value <=@max and [min]<>0 ---获取下一级节点的范围
end
select @result ---子孙数量
递归的次数为树的深度,即树有多深就递归多少次
(二)、效率分析:
① ② ③
注:粉红线是查询路径
上图分别为一般树、分级树、左右值树的查询方式图解,一般树迭代查询parentid=id的节点,分级树迭代查询每层的边界值来确定下一层范围,左右树通过左右值来确定子孙范围,所以效率对比为:
一般树<=分级树<=左右值树
(2)为某节点添加子孙节点
(一)、算法
注:粉色为受影响的数据
例:c点添h
代码如下:
1 declare @name varchar(50) 2 set @name='c' 3 4 declare @lv int,@min int,@max int,@newvalue int,@fvalue int ---@newvalue为h节点的value值,@fvalue为c节点value 5 select @lv=lv,@min=[min],@max=[max],@fvalue =value from testtree where name =@name 6 if(@max>0) ---c节点已有孩子时 7 begin 8 set @newvalue =@max+1 9 end 10 else 11 begin 12 if((select COUNT (*) from testtree where lv=@lv and [max]>0 and value <@fvalue)>0)---看看要c节点有没有哥哥是有孩子的 13 begin 14 select @newvalue =max([max])+1 from testtree where lv=@lv and value <@fvalue ---有的话h点就排在哥哥的孩子后面 15 end 16 else 17 begin 18 set @newvalue =1 ---没的话,h就当@lv+1层的大哥 19 end 20 end 21 22 SET XACT_ABORT ON 23 BEGIN TRANSACTION 24 update testtree set [min]+=1,[max]+=1 where lv=@lv and value >@fvalue ---大哥添子,小弟的孩子排名降位 25 update testtree set value +=1 where lv=@lv+1 and value >=@newvalue ---小弟的孩子降位 26 insert into testtree (name,lv,[min],[max],value) values ('h',@lv+1,0,0,@newvalue) 27 if(@min>0) ---更新c点min和max值 28 begin 29 update testtree set [max]=@newvalue where name=@name 30 end 31 else 32 begin 33 update testtree set [min]=@newvalue ,[max]=@newvalue where name =@name 34 end 35 36 COMMIT TRANSACTION 37 SET XACT_ABORT OFF
(二)、效率分析:
一般树直接插入,无需更新其他点;分级树插入后需更新插入层以及父层的节点,左右树在插入后需更新插入节点右边所有的树节点(不了解的可以点击前言处的链接去看下,此处不多说);所以效率对比为:
左右树<=分级树<一般树
(3)删除某节点
(一)、算法
例如删除c点,如图示:
注:粉色为删除点,蓝色为受影响点
代码如下:
1 declare @name varchar(50) 2 set @name ='c' 3 4 declare @lv int,@min int,@max int,@value int,@result int,@nextmin int,@nextmax int,@minvalue int,@pminvalue int,@delenum int 5 select @lv=lv,@nextmin=[min],@nextmax=[max],@value=value from testtree where name =@name 6 set @result =0 7 SET XACT_ABORT ON 8 BEGIN TRANSACTION 9 delete testtree where name =@name ---删除节点 10 update testtree set value -=1 where lv=@lv and value>@value ---删除节点所在层的并且在删除节点右边的节点的value-1 11 if(@lv>1) ---该节点有父节点时 12 begin 13 if((select [max] from testtree where lv=@lv-1 and [max]>=@value and [min] <=@value)=1) ---该父节点只有1个子节点时 14 begin 15 update testtree set [min]=0,[max]=0 where lv=@lv-1 and [max]>=@value and [min] <=@value 16 end 17 else 18 begin 19 update testtree set [max]-=1 where lv=@lv-1 and [max]>=@value and [min] <=@value 20 end 21 end 22 set @result =1 23 set @minvalue =@value 24 while(@nextmin<>0) ---循环删除c的子孙 25 begin 26 set @min=@nextmax 27 set @max =@nextmax ---删除节点的范围 28 set @pminvalue =@minvalue ---上级删除节点中的最小value 29 set @lv +=1 30 31 select @minvalue =min(value) , @delenum=COUNT (*) from testtree where lv =@lv and value >=@min and value <=@max ---获取要删除节点的最小value和要删除的节点数 32 select @nextmin = min([min]),@nextmax =max([max]) from testtree where lv =@lv and value >=@min and value <=@max and [min]<>0 ---获取下一层节点的范围 33 34 set @result +=@delenum ---删除的总节点数量 35 36 delete testtree where lv=@lv and value >=@min and value <=@max ----删除这一层节点 37 update testtree set value -=@delenum where lv=@lv and value >@minvalue ---把这层大于删除节点的minvalue的节点value值减少 38 update testtree set [min]-=@delenum ,[max]-=@delenum where lv=@lv-1 and value >=@pminvalue ---把上一层的大于父删除节点minvalue的节点的min、max减少 39 40 end 41 COMMIT TRANSACTION 42 SET XACT_ABORT OFF 43 select @result as delesun ---子孙数量
(二)、效率分析
删除的影响还是用图示明显点,请看下图:
注:粉色为删除点,蓝色为受影响点
因选择的点问题所以分级树和左右值树效率一样,但分级树影响范围是的是删除节点的父节点到删除节点最底层子孙节点的所有右边节点,而左右值树影响范围是的删除节点的最底层子孙到删除节点的根节点的所有右边节点,所以效率比较为:
左右值树<=分级树<一般树
三、总结
通过上面的算法分析,这种设计基本符合我的需求,其它的如节点移动的算法就不多说了。上面的算法应该还可以优化点,有兴趣的可以进行优化,最好可以发下出来,不胜感激