问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

数据结构,树的常用存储方式

发布网友 发布时间:2022-04-24 08:29

我来回答

3个回答

懂视网 时间:2022-05-01 20:03

最近在开发jSqlBox过程中,研究树形结构的操作,突然发现一种新的树结构数据库存储方案,在网上找了一下,没有找到雷同的(也可能是花的时间不够),现介绍如下: 目前常见的树形结构数据库存储方案有以下四种,但是都存在一定问题:
1)Adjacency List::记录父节点。优点是简单,缺点是访问子树需要遍历,发出许多条SQL,对数据库压力大。
2)Path Enumerations:用一个字符串记录整个路径。优点是查询方便,缺点是插入新记录时要手工更改此节点以下所有路径,很容易出错。
3)Closure Table:专门一张表维护Path,缺点是占用空间大,操作不直观。
4)Nested Sets:记录左值和右值,缺点是复杂难操作。
以上方法都存在一个共同缺点:操作不直观,不能直接看到树结构,不利于开发和调试。
本文介绍的方法我暂称它为“简单粗暴多列存储法”,它与Path Enumerations有点类似,但区别是用很多的数据库列来存储一个占位符(1或空值),如下图(https://github.com/drinkjava2/Multiple-Columns-Tree/blob/master/treemapping.jpg) 左边的树结构,映射在数据库里的结构见右图表格:
技术分享

各种SQL操作如下:

1.获取(或删除)指定节点下所有子节点,已知节点的行号为"X",列名"cY":
select *(or delete) from tb where 
 line>=X and line<(select min(line) from tb where line>X and (cY=1 or c(Y-1)=1 or c(Y-2)=1 ... or c1=1))
例如获取D节点及其所有子节点:
select * from tb where line>=7 and line< (select min(line) from tb where line>7 and (c2=1 or c1=1)) 
删除D节点及其所有子节点:
delete from tb where line>=7 and line< (select min(line) from tb where line>7 and (c2=1 or c1=1)) 

仅获取D节点的次级所有子节点:
select * from tb where line>=7 and c3=1 and line< (select min(line) from tb where line>7 and (c2=1 or c1=1)) 

2.查询指定节点的根节点, 已知节点的行号为"X",列名"cY":
select * from tb where line=(select max(line) from tb where line<=X and c1=1)
例如查I节点的根节点:
select * from tb where line=(select max(line) from tb where line<=12 and c1=1) 

3.查询指定节点的上一级父节点, 已知节点的行号为"X",列名"cY":
select * from tb where line=(select max(line) from tb where line<X and c(Y-1)=1)
例如查L节点的上一级父节点:
select * from tb where line=(select max(line) from tb where line<11 and c3=1) 

3.查询指定节点的所有父节点, 已知节点的行号为"X",列名"cY":
select * from tb where line=(select max(line) from tb where line<X and c(Y-1)=1)
union select * from tb where line=(select max(line) from tb where line<X and c(Y-2)=1)
...
union select * from tb where line=(select max(line) from tb where line<X and c1=1)
例如查I节点的所有父节点:
select * from tb where line=(select max(line) from tb where line<12 and c2=1)
union select * from tb where line=(select max(line) from tb where line<12 and c1=1) 

4.插入新节点:
视需求而定,例如在J和K之间插入一个新节点T:
update tb set line=line+1 where line>=10;
insert into tb (line,id,c4) values (10,‘T‘,1)
这是与Path Enumerations模式最大的区别,插入非常方便,只需要利用SQL将后面的所有行号加1即可,无须花很大精力维护path字串, 
不容易出错。
另外如果表非常大,为了避免update tb set line=line+1 造成全表更新,影响性能,可以考虑增加
一个GroupID字段,同一个根节点下的所有节点共用一个GroupID,所有操作均在groupID组内进行,例如插入新节点改为:
update tb set line=line+1 where groupid=2 and line>=8;
insert into tb (groupid,line,c4) values (2, 8,‘T‘)
因为一个groupid下的操作不会影响到其它groupid,对于复杂的增删改操作甚至可以在内存中完成操作后,一次性删除整个group的内容 
并重新插入一个新group即可。

总结:
以上介绍的这种方法优点有:
1)直观易懂,方便调试,是所有树结构数据库方案中唯一所见即所得,能够直接看到树的形状的方案,空值的采用使得树形结构一目了然。
2)SQL查询、删除、插入非常方便,没有用到Like语法。
3)只需要一张表
4)兼容所有数据库
5)占位符即为实际要显示的内容应出现的地方,方便编写Grid之类的表格显示控件

缺点有 1)不是无限深度树,数据库最大允许列数有限制,通常最多为1000,这导致了树的深度不能超过1000,而且考虑到列数过多对性能也有影响, 使用时建议定一个比较小的深度限制例如100。
2)SQL语句比较长,很多时候会出现c9=1 or c8=1 or c7=1 ... or c1=1这种n阶乘式的查询条件
3)树的节点整体移动操作比较麻烦,需要将整个子树平移或上下称动,当节点须要经常移动时,不建议采用这种方案。对于一些只增减,不常移动节点的应用如论坛贴子和评论倒比较合适。
4)列非常多时,空间占用有点大。

以下为追加内容,是在前述基础上,一种更简单的无限深度树方案

突 然发现上面的方法还是太笨了,如果不用多列而是只用一个列来存储深度等级,则可以不受数据库列数限制,从而进化为无限深度树,虽然不再具有所见即所得的效 果,但是在性能和简单性上要远远超过上述“简单粗暴多列存储法”,暂时给它取名"朱氏深度树V2.0法"(备注:如果已有人发明了这个方法,删掉前两个字 就好了),方法如下: 如下图 (https://github.com/drinkjava2/Multiple-Columns-Tree/blob/master/treemappingv2.png) 左边的树结构,映射在数据库里的结构见右图表格,注意每个表格的最后一行必须有一个END标记,level设为0:

技术分享

1.获取指定节点下所有子节点,已知节点的行号为X,level为Y, groupID为Z
select * from tb2 where groupID=Z and 
 line>=X and line<(select min(line) from tb where line>X and level<=Y and groupID=Z)
例如获取D节点及其所有子节点:
select * from tb2 where groupID=1 and 
 line>=7 and line< (select min(line) from tb2 where groupid=1 and line>7 and level<=2)
删除和获取相似,只要将sql中select * 换成delete即可。

仅获取D节点的次级所有子节点:(查询条件加一个level=Y+1即可):
select * from tb2 where groupID=1 and 
 line>=7 and level=3 and line< (select min(line) from tb2 where groupid=1 and line>7 and level<=2) 

2.查询任意节点的根节点, 已知节点的groupid为Z
select * from tb2 where groupID=Z and line=1 (或level=1) 

3.查询指定节点的上一级父节点, 已知节点的行号为X,level为Y, groupID为Z
select * from tb2 where groupID=Z and 
 line=(select max(line) from tb2 where groupID=Z and line<X and level=(Y-1))
例如查L节点的上一级父节点:
select * from tb2 where groupID=1 
 and line=(select max(line) from tb2 where groupID=1 and line<11 and level=3) 

4.查询指定节点的所有父节点, 已知节点的行号为X,深度为Y:
select * from tb2 where groupID=Z and 
 line=(select max(line) from tb2 where groupID=Z and line<X and level=(Y-1))
union select * from tb2 where groupID=Z and 
 line=(select max(line) from tb2 where groupID=Z and line<X and level=(Y-2))
...
union select * from tb2 where groupID=Z and 
 line=(select max(line) from tb2 where groupID=Z and line<X and level=1)
例如查I节点的所有父节点:
select * from tb2 where groupID=1 and 
 line=(select max(line) from tb2 where groupID=1 and line<12 and level=2)
union select * from tb2 where groupID=1 and 
 line=(select max(line) from tb2 where groupID=1 and line<12 and level=1)

5.插入新节点:例如在J和K之间插入一个新节点T:
update tb2 set line=line+1 where groupID=1 and line>=10;
insert into tb (groupid,line,id,level) values (1,10,‘T‘,4);

总结: 此方法优点有:
1) 是无限深度树
2) 虽然不象第一种方案那样具有所见即所得的效果,但是依然具有直观易懂,方便调试的特点。
3) 能充分利用SQL,查询、删除、插入非常方便,SQL比第一种方案简单多了,也没有用到like模糊查询语法。
4) 只需要一张表。
5) 兼容所有数据库。
6) 占用空间小

缺点有:
1)树的节点整体移动操作有点麻烦, 适用于一些只增减,不常移动节点的场合如论坛贴子和评论等。当确实需要进行复杂的移动节点操作时,一种方案是在内存中进行整个树的操作并完成排序,操作完成后删除整个旧group再整体将新group一次性批量插入数据库。

 

1月22日补充:
节点的移动操作有点麻烦,只是相对于查询/删除/插入来说,并不是说难上天了。例如在MySQL下移动整个B节点树到H节点下,并位于J和K之间的操作如下:
update tb2 set tempno=line*1000000 where groupid=1;  
set @nextNodeLine=(select min(line) from tb2 where groupid=1 and line>2 and level<=2);  
update tb2 set tempno=9*1000000+line, level=level+2 where groupID=1 and line>=2 and line< @nextNodeLine;  
set @mycnt=0;  
update tb2 set line=(@mycnt := @mycnt + 1) where groupid=1 order by tempno;  
上例需要在表中新增一个名为tempno的整数类型列, 这是个懒人算法,虽然简单明了,但是对整棵树进行了重新排序,所以效率并不高。 在需要频繁移动节点的场合下,用Adjacency List方案可能更合适一些。

如 果需要频繁移动节点的场合,又想保留方案2高效查询的优点,还有一种方案就是再添加一个父节点pid字段和两个辅助字段tempno和 temporder用于排序,(暂时称其为“深度树V3.0法"), 这样相当于V2.0法和Adjacency List模式的合并了,优点是每次移动节点,只需要更改PID即可,不需要复杂的算法,一次可以任意移动、增加、删除多个节点,最后统一调用以下算法简单 地进行一下重排序即可,下面这个示例完整演示了一个Adjacency List模式到V2.0模式的转换,这相当于一个重新给树建查询索引的过程:

技术分享

create table tb3 (
id varchar(10),
comments varchar(55),
pid varchar(10),
line integer,
level integer,
tempno bigint,
temporder integer
)

insert into tb3 (id,comments,Pid) values(‘A‘,‘found a bug‘,null);
insert into tb3 (id,comments,Pid) values(‘B‘,‘is a worm‘,‘A‘);
insert into tb3 (id,comments,Pid) values(‘C‘,‘no‘,‘A‘);
insert into tb3 (id,comments,Pid) values(‘D‘,‘is a bug‘,‘A‘);
insert into tb3 (id,comments,Pid) values(‘E‘,‘oh, a bug‘,‘B‘);
insert into tb3 (id,comments,Pid) values(‘F‘,‘solve it‘,‘B‘);
insert into tb3 (id,comments,Pid) values(‘G‘,‘careful it bites‘,‘C‘);
insert into tb3 (id,comments,Pid) values(‘H‘,‘it does not bit‘,‘D‘);
insert into tb3 (id,comments,Pid) values(‘I‘,‘found the reason‘,‘D‘);
insert into tb3 (id,comments,Pid) values(‘J‘,‘solved‘,‘H‘);
insert into tb3 (id,comments,Pid) values(‘K‘,‘uploaded‘,‘H‘);
insert into tb3 (id,comments,Pid) values(‘L‘,‘well done!‘,‘H‘);

set @mycnt=0;
update tb3 set line=0,level=0, tempno=0, temporder=(@mycnt := @mycnt + 1) order by id;
update tb3 set level=1, line=1 where pid is null;

update tb3 set tempno=line*10000000 where line>0; 
update tb3 a, tb3 b set a.level=2, a.tempno=b.tempno+a.temporder where a.level=0 and 
a.pid=b.id and b.level=1;
set @mycnt=0;
update tb3 set line=(@mycnt := @mycnt + 1) where level>0 order by tempno;

update tb3 set tempno=line*10000000 where line>0; 
update tb3 a, tb3 b set a.level=3, a.tempno=b.tempno+a.temporder where a.level=0 and 
a.pid=b.id and b.level=2;
set @mycnt=0;
update tb3 set line=(@mycnt := @mycnt + 1) where level>0 order by tempno;

update tb3 set tempno=line*10000000 where line>0; 
update tb3 a, tb3 b set a.level=4, a.tempno=b.tempno+a.temporder where a.level=0 and 
a.pid=b.id and b.level=3;
set @mycnt=0;
update tb3 set line=(@mycnt := @mycnt + 1) where level>0 order by tempno;

 
以上算法利用了SQL的功能,将原来可能需要非常多SQL递归查询的过程转变成了有限次数(=树最大深度)的SQL操作,为了突出算法,以上示例 假设只有一个根节点,删除了groupid和endtag,实际使用中要完善一下这个细节, order by id也可改成以其它字段排序。因时间关系我就不给出V2.0模式到Adjacency List模式逆推的算法了(也即pid为空,根据V2.0表格倒过来给pid赋值的过程),不过这个算法倒不重要,因为通常v3.0表中每一行会一直保存 着一个pid)。
总结一下:
Adjacency List模式:移/增/删节点方便,查询不方便
深度树V2.0模式:查询方便,增/删节点方便,但存在效率问题,移动节点不方便
深度树V3.0模式:移/增/删节点方便,查询方便,缺点是每次移/增/删节点后要重建line和level值以供查询用。它是结合了上两种模式 的合并体,并可以根据侧重,随时在这两种模式(修改模式和查询模式)间切换。v3.0法相当于给Adjacency List模式设计了一个查询索引。

 

发现几种树结构数据库存储方案

标签:pre   指定   字段排序   where   映射   sql查询   四种   更新   比较   

热心网友 时间:2022-05-01 17:11

存入文本文件,每行:孩子节点-父节点。
这样也方便用Hadoop进行处理。追问能给出一颗树,根据这三种表示方法画出来吗?让我看看区别在哪。这是一道简答题,不是程序题

热心网友 时间:2022-05-01 18:29

在计算机中,数据的存储结构可以采用如下四种方法来实现。
1、顺序存储方式:顺序存储方式就是在一块连续的存储区域一个接着一个的存放数据。顺序存储方式把逻辑上相邻的节点存储在物理位置撒花姑娘相邻的存储单元里,节点间的逻辑关系由存储单元的邻接关系来体现。顺序存储方式也称为顺序存储结构,一般采用数组或结构数组来描述。
2、链接存储方式:链接存储方式比较灵活,不要求逻辑上相邻的节点在物理位置上相邻,节点间的逻辑关系由附加的引用字段来表示。一个节点的引用字段往往指向下一个节点的存放位置。链接存储方式也成为链式存储结构。
3、索引存储方式:索引存储方式是采用附加的索引表的方式来存储节点信息的一种存储方式。索引表由若干索引项组成。索引存储方式中索引项的一般形式为(关键字、地址)。其中,关键字是能够唯一标识一个节点的数据项。索引存储方式还可以细分为如下两类。
稠密索引:这种方式中每个节点在索引表中都有一个索引项,其中索引项的地址知识节点所在的存储位置。
稀疏索引:这种方式中一组节点在索引表中只对应一个索引项。其中,索引项的地址指示一组节点的起始存储位置。
4、散列存储方式:散列存储方式是根据节点的关键字直接计算出该节点的存储地址的一种存储方式。
在实际应用中,往往需要根据具体的数据结构来决定采用哪种存储方式。同一逻辑结构采用不同的存储方法,可以得到不同的存储结构。而且者4中基本存储方法,既可以单独使用,也可以组合起来对数据结构进行存储描述。
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
vivo y3t手机能拨打和接听电话,不能收发短信 vivoy3t手机突然接收不到短信 vivoy3短信消息怎么提醒 无奈什么意思是什么 怎样让炒出的丝瓜不发黑? 钟表是以什么计量时间 钟表以( )、()、( )计量时间。 钟表以( )、()、( )单位计量时间 紫荆花开放时间 《青春 须臾成殇》渭伊的txt全集下载地址 链式存储结构的特点是利用什么来表示数据元素之间的逻辑关系 线性表的链式存储结构是一种()存储结构? 分别就栈的顺序存储结构和链式存储结构实现栈的各种基本操作。_百度知 ... 线性表的链式存储结构定义及基本操作 二叉树通常链式结构还有什么结构 线性表链式存储结构的优点和缺点有什么? 线性表的链式存储结构主要包括哪三种形式。在线等。。。 链式存储结构的特点是利用什么来表示数据元素之间的逻辑关系? 存储结构有哪些? 笔记本电脑怎么投屏到lg c7上 笔记本LG屏幕 lg笔记本电脑显示屏用着怎么样谁知道 LG显示器连接笔记本 惠普笔记本用的是LG的显示器 质量怎么样啊? 宝宝注意力不集中,该做哪些训练改善呢? 外卖小哥电话? 外卖电话 为什么年底要账难度越来越大 年底了,客户不给结账,怎么讨账?讨账有几种方法? 过年讨债的经典台词 链式队列存储结构的定义及基本操作 数据结构线性表的单链表存储结构 百度网盘文件显示网页转存的意思 百度云转存文件 在百度网盘买的课程每次都让转存有什么用? 为什么从百度云转存微云的文件老是提示云端转存中 百度网盘下载和转存有啥区别? 百度网盘转存不了是什么原因? 115网盘上传文件后显示待续传,是什么意思?还有提取文件时显示文件转存中,请稍后下载又是什么意思? 百度网盘转存不了文件,什么原因 百度云盘文件怎么转存到另一个百度网盘里 什么叫转存到网盘 网盘是什么意思 自己要申请吗?存到网盘以后用起来方便吗? 百度网盘在升级中是什么意思 果绿色厚窗帘布配啥色杆子好看? 什么颜色的布或什么样的布料挡太阳光或遮荫效果比较好? 小米蓝牙共享网络是什么意思,可以通过台式电脑蓝牙连接小米手机wifi上网吗? 台式电脑不知道wifi密码,怎么连wifi 新房安装暖气片注意事项是什么? 暖气片怎样安装效果好?要注意什么问题? 工地用暖气片安装注意哪些啊?