[TREE]采用左右值编码来存储无限分级树形结构的数据库表设计
树形结构的数据库设计
树形结构的数据库表Schema设计程序设计过程中,我们常常用树形结构来表征某些数据的关联关系,如企业上下级部门、栏目结构、商品分类等等,通常而言,这些树状结构需要借助于数据库完成持久化。
然而目前的各种基于关系的数据库,都是以二维表的形式记录存储数据信息,因此是不能直接将Tree存入DBMS,设计合适的Schema及其对应的CRUD算法是实现关系型数据库中存储树形结构的关键。
理想中树形结构应该具备如下特征:数据存储冗余度小、直观性强;检索遍历过程简单高效;节点增删改查CRUD 操作高效。
无意中在网上搜索到一种很巧妙的设计,原文是英文,看过后感觉有点意思,于是便整理了一下。
本文将介绍两种树形结构的Schema设计方案:一种是直观而简单的设计思路,另一种是基于左右值编码的改进方案。
一、基本数据本文列举了一个食品族谱的例子进行讲解,通过类别、颜色和品种组织食品,树形结构图如下:二、继承关系驱动的Schema设计对树形结构最直观的分析莫过于节点之间的继承关系上,通过显示地描述某一节点的父节点,从而能够建立二维的关系表,则这种方案的Tree表结构通常设计为:{Node_id,Parent_id},上述数据可以描述为如下图所示:这种方案的优点很明显:设计和实现自然而然,非常直观和方便。
缺点当然也是非常的突出:由于直接地记录了节点之间的继承关系,因此对Tree的任何CRUD操作都将是低效的,这主要归根于频繁的“递归”操作,递归过程不断地访问数据库,每次数据库IO都会有时间开销。
当然,这种方案并非没有用武之地,在Tree规模相对较小的情况下,我们可以借助于缓存机制来做优化,将Tree的信息载入内存进行处理,避免直接对数据库IO操作的性能开销。
三、基于左右值编码的Schema设计在基于数据库的一般应用中,查询的需求总要大于删除和修改。
为了避免对于树形结构查询时的“递归”过程,基于Tree的前序遍历设计一种全新的无递归查询、无限分组的左右值编码方案,来保存该树的数据。
java树形结构表设计方案
java树形结构表设计方案全文共四篇示例,供读者参考第一篇示例:一、背景介绍在软件开发中,树形结构是一种常见的数据结构,它能够以层次关系来表示数据元素之间的结构。
在实际开发中,树形结构通常用在类似组织结构、目录结构、分类信息等场景中。
而在Java中,我们可以利用树形结构来设计数据库表,以达到更高效的数据存储和管理目的。
二、设计方案1. 数据表设计我们可以通过建立两张表来实现树形结构的存储:一张是节点表,另一张是关系表。
节点表用于存储树形结构中的各个节点信息,包括节点ID、节点名称、父节点ID等相关信息;关系表用于存储节点之间的关系,即父子关系。
节点表的设计如下:```CREATE TABLE node (node_id INT PRIMARY KEY,node_name VARCHAR(50),parent_id INT);```2. 数据操作在设计好表结构后,我们可以通过一些字段来操作树形结构数据。
可以通过根节点的ID来获取整棵树的信息,或者通过节点的ID来获取其父节点和子节点的信息。
我们可以利用递归算法来实现对树形结构的操作。
要获取特定节点的所有子节点,可以通过以下伪代码实现:```function getChildren(node_id) {List<Node> children = new ArrayList<>();for (Node node : nodes) {if (node.parent_id == node_id) {children.add(node);children.addAll(getChildren(node.node_id));}}return children;}```3. 查询优化在处理树形结构数据时,由于节点之间存在父子关系,可能会出现多层嵌套查询的情况。
为了提高查询效率,我们可以采用一些优化措施,例如建立索引、使用缓存等。
对于查询来说,我们可以借助数据库的索引功能来加快查询速度。
TREE采用左右值编码来存储无限分级树形结构的数据库
采用左右值编码来存储无限分级树形结构的数据库表设计之前我介绍过一种按位数编码保存树形结构数据的表设计方法,详情见:浅谈数据库设计技巧(上)该设计方案的优点是:只用一条查询语句即可得到某个根节点及其所有子孙节点的先序遍历。
由于消除了递归,在数据记录量较大时,可以大大提高列表效率。
但是,这种编码方案由于层信息位数的限制,限制了每层能所允许的最大子节点数量及最大层数。
同时,在添加新节点的时候必须先计算新节点的位置是否超过最大限制。
上面的设计方案必须预先设定类别树的最大层数以及最大子节点数,不是无限分级,在某些场合并不能采用,那么还有更完美的解决方案吗?通过google的搜索,我又探索到一种全新的无递归查询,无限分级的编码方案——左右值。
原文的程序代码是用php写的,但是通过仔细阅读其数据库表设计说明及相关的sql语句,我彻底弄懂了这种巧妙的设计思路,并在这种设计中新增了删除节点,同层平移的需求(原文只提供了列表及插入子节点的sql语句)。
下面我力图用比较简短的文字,少量图表,及相关核心sql语句来描述这种设计方案:首先,我们弄一棵树作为例子:商品|---食品| |---肉类| | |--猪肉| |---蔬菜类| |--白菜|---电器|--电视机|--电冰箱select count(*) from tree where lft <= 2 and rgt >= 11为了方便列表,我们可以为tree表建立一个视图,添加一个层数列,该类别的层数可以写一个自定义函数来计算。
该函数如下:CREATE FUNCTION dbo.CountLayer(@type_id int)RETURNS intASbegindeclare@result intset@result=0declare@lft intdeclare@rgt intif exists (select1from tree where type_id=@type_id)beginselect@lft=lft,@rgt=rgt from tree where type_id=@type_idselect@result=count(*) from tree where lft <=@lft and rgt >=@rgtendreturn@resultendGO然后,我们建立如下视图:CREATE VIEW dbo.TreeViewASSELECT type_id, name, lft, rgt, dbo.CountLayer(type_id) AS layer FROM dbo.tree ORDE R BY lftGO()AS declare declare ifgo假定我们要在节点“肉类”下添加一个子节点“牛肉”,该树将变成:1商品18+2+--------------------------------------------+2食品11+2 12+2电器17+2+-----------------+ +-------------------------+3肉类6+2 7+2蔬菜类10+2 13+2电视机14+2 15+2电冰箱16+2 +-------------+4猪肉5 6牛肉78+2白菜9+2看完上图相应节点左右值的变化后,相信大家都知道该如何写相应的sql脚本吧?下面我给出相对完整的插入子节点的存储过程:CREATE PROCEDURE[dbo].[AddSubNodeByNode](@type_id int,@name varchar(50))ASdeclare@rgt intif exists (select1from tree where type_id=@type_id)beginSET XACT_ABORT ONBEGIN TRANSACTIONselect@rgt=rgt from tree where type_id=@type_idupdate tree set rgt=rgt+2where rgt>=@rgtupdate tree set lft=lft+2where lft>=@rgtinsert into tree (name,lft,rgt) values (@name,@rgt,@rgt+1)COMMIT TRANSACTIONSET XACT_ABORT OFFend然后,我们删除节点“电视机”,再来看看该树会变成什么情况:1商品20-2+-----------------------------------+2食品13 14电器19-2+-----------------+3肉类8 9蔬菜类12 17-2电冰箱18-2+----------+4猪肉5 6牛肉7 10白菜11相应的存储过程如下:CREATE PROCEDURE[dbo].[DelNode]@type_id intASdeclare@lft intdeclare@rgt intif exists (select1from tree where type_id=@type_id)beginSET XACT_ABORT ONBEGIN TRANSACTIONselect@lft=lft,@rgt=rgt from tree where type_id=@type_iddelete from tree where lft>=@lft and rgt<=@rgtupdate tree set lft=lft-(@rgt-@lft+1) where lft>@lftupdate tree set rgt=rgt-(@rgt-@lft+1) where rgt>@rgtCOMMIT TRANSACTIONSET XACT_ABORT OFFEnd注意:因为删除某个节点会同时删除该节点的所有子孙节点,而这些被删除的节点的个数为:(被删节点的右值-被删节点的左值+1)/2,而任何一个节点同时具有唯一的左值和唯一的右值,故删除作废节点后,其他相应节点的左、右值需要调整的幅度应为:减少(被删节点的右值-被删节点的左值+1)。
采用预排序遍历树算法实现无限分级树形结构的设计及应用
这里我用一个简单商品 目 录作 为我f 的示例数据 .以下是它的结 『 J .
构 模型 .
再 数一遍 ,注意移动你的 手指 ).这些数字标明了各个节点之 的关 , 系 ,” 砥装 ” 的号是3 ,它 是 ” 和4 服装” — 8 子孙节点 I .我 仃 ll的 川样 J 可 以看到 所有左值 大于2 和右值 小于7 的节点都是” 男式” — 的子孙节 27
P ̄ e l r ,t
旗袍
l 1
】 2
3
童 装
上 裹
l 4
1 5
1 7
1 6 3
般装
我们 用” 和” t L 唱” 来表示左 右字段 ( 出于“ f 和” g t S L l t r h” Q 中 e” i 在 有特 殊的 意义 );另外 ,J 入了 ” p ” 段 ,来表示 当前节点 的深 J u d t字 eh 度 ,这在实际应用 中的 许多地方 ,都能提供更方便的功能 我f 现在 『 J 可 以从数据 库中获取数据了 ,例如我们需要得到” 男式 ” 项下的所有节 点 就 可 以 这 样 写 查 瑚 语 句 : S L T F OM re WHE E I E EC R t e R f t
式 ”的左侧写上2 后继 续前进 ,沿着整 个树 的边缘给每 一个节点都 然
标 上左侧 和右侧的数字 ,最 后一个数字是 标在 “ 装”右 侧的 1 服 8 , 在 下面的这张图 中你可 以看到整个标好了数字的 多级结构 ( 没有看 懂 ? 你的手指指 着数字从 l 数到 l就明 白怎么四事 了 还 不明 白, 8
关键 词 预排序遍历 无限分美 树 形结构 随 着汁算机的发展 ,尤其是软件技术的广泛应用 ,产品分类 、多
级的 树状 结构 的沦坛 、 邮件 列表等 许多地方我 f 都 会遇 到这佯 的『 『 J u J 题 :如何存储 多级结构 的数据 ?如何 让多级结构的操作更高效 、 更准
树状数组维护区间最值模板
树状数组维护区间最值模板1. 什么是树状数组?树状数组(Fenwick Tree),也称为二进制索引树(Binary Indexed Tree,BIT),是一种用于高效计算前缀和、区间和以及维护区间最值的数据结构。
它可以在O(logn)的时间复杂度内完成单点更新和查询操作。
2. 树状数组的原理树状数组的核心思想是利用二进制位的特性来进行高效计算。
它将原始序列分解为多个子序列,并利用每个子序列的前缀和来表示该子序列中元素的累加和。
具体地,设原始序列为a[1…n],树状数组C[1…n]用于存储每个子序列的前缀和。
其中,C[i]表示以a[i]为末尾元素的子序列的累加和。
对于某个位置i,其对应的父节点位置可以通过将i的二进制表示中最低位1变为0来计算得到。
例如,对于二进制表示为1010(十进制为10)的位置i,其父节点位置为1000(十进制为8)。
树状数组支持两种操作:单点更新和区间查询。
•单点更新:当某个位置i发生变化时,需要更新其对应的所有父节点位置的值。
具体操作为将i的二进制表示中最低位1变为0,并依次向上更新。
•区间查询:要求计算区间[l, r]的和(或最值),可以通过计算C[r] - C[l-1]得到。
其中,C[r]表示以a[r]为末尾元素的子序列的累加和,C[l-1]表示以a[l-1]为末尾元素的子序列的累加和。
这样计算得到的差值即为区间[l, r]内元素的累加和。
3. 树状数组维护区间最值模板树状数组不仅可以用于维护前缀和,还可以用于维护区间最值。
以下是树状数组维护区间最值的模板代码:const int MAXN = 100000; // 树状数组大小int a[MAXN]; // 原始序列int C[MAXN]; // 树状数组// 单点更新操作void update(int pos, int val) {while (pos <= MAXN) {C[pos] = max(C[pos], val); // 维护区间最大值,如果是求和则改为C[pos]+= valpos += lowbit(pos); // 计算下一个父节点位置}}// 区间查询操作int query(int pos) {int res = 0;while (pos > 0) {res = max(res, C[pos]); // 维护区间最大值,如果是求和则改为res+=C[pos] pos -= lowbit(pos); // 计算下一个查询位置}return res;}// 计算pos的二进制表示中最低位1所对应的值int lowbit(int pos) {return pos & -pos;}4. 树状数组维护区间最值模板的使用示例以下是一个使用树状数组维护区间最值模板的示例,用于求解原始序列中某个区间内的最大值:#include <iostream>using namespace std;const int MAXN = 100000; // 树状数组大小int a[MAXN]; // 原始序列int C[MAXN]; // 树状数组// 单点更新操作void update(int pos, int val) {while (pos <= MAXN) {C[pos] = max(C[pos], val);pos += lowbit(pos);}}// 区间查询操作int query(int pos) {int res = 0;while (pos > 0) {res = max(res, C[pos]);pos -= lowbit(pos);}return res;}// 计算pos的二进制表示中最低位1所对应的值int lowbit(int pos) {return pos & -pos;}int main() {int n; // 序列长度cin >> n;for (int i = 1; i <= n; i++) {cin >> a[i];update(i, a[i]);}int q; // 查询次数cin >> q;while (q--) {int l, r; // 查询区间cin >> l >> r;int max_val = query(r) - query(l-1);cout << "区间最大值:" << max_val << endl;}return 0;}5. 总结树状数组是一种高效的数据结构,适用于解决多种问题,包括维护前缀和、区间和以及维护区间最值等。
mysql生成树形结构知识点
mysql生成树形结构知识点
生成树形结构是指根据某个表中的数据,构建出一个树状的结构,其中每个节点都有一个或多个子节点。
在MySQL中,可以使用以下几种方法来生成树形结构:
1. 递归查询:使用递归查询可以直接从表中查询出树形结构。
一般使用WITH RECURSIVE语法来实现,可以不断地迭代查询,直到找到根节点为止。
2. 嵌套集模型(Nested Set Model):嵌套集模型是一种将树状结构表示为一系列的左右值的方法。
根据左右值可以方便地查询到某个节点的所有子节点,以及该节点的父节点。
3. 路径枚举模型(Path Enumeration Model):路径枚举模型是一种将树状结构表示为路径字符串的方法。
每个节点都有一个路径字符串,包含了从根节点到该节点的路径。
可以通过匹配路径字符串来查询子节点和父节点。
4. 存储过程:使用存储过程来逐层构建树形结构。
可以通过循环迭代查询,并逐层插入数据到结果表中,最终构建出树形结构。
以上是一些常见的方法,每种方法都有其适用的场景和使用方式。
选择合适的方法取决于具体的需求和数据结构。
数据结构——用C语言描述 第六章 树状结构
数据结构——用C语言描述第六章树状结构在计算机科学中,数据结构是组织和存储数据的方式,以便能够高效地访问和操作这些数据。
树状结构是一种重要的数据结构,它在许多算法和应用中都发挥着关键作用。
树状结构就像是一棵倒立的树,有一个根节点,然后从根节点向下延伸出许多分支,每个分支又可以进一步延伸出更多的分支。
这种结构使得数据的组织和管理更加清晰和高效。
在树状结构中,每个节点可以包含数据以及指向其子节点的指针。
节点之间通过这些指针形成层次关系。
树状结构的一个重要特点是,除了根节点外,每个节点都有且仅有一个父节点。
二叉树是树状结构中最常见的一种形式。
二叉树中的每个节点最多有两个子节点,分别称为左子节点和右子节点。
二叉搜索树是一种特殊的二叉树,它具有一定的规则。
对于二叉搜索树中的每个节点,其左子树中的所有节点的值都小于该节点的值,而其右子树中的所有节点的值都大于该节点的值。
这种特性使得在二叉搜索树中进行查找、插入和删除操作的效率很高。
例如,如果要在二叉搜索树中查找一个值,我们可以从根节点开始。
如果要查找的值小于当前节点的值,就向左子树继续查找;如果大于当前节点的值,就向右子树继续查找。
直到找到目标值或者确定该值不存在。
用 C 语言来实现二叉搜索树,首先需要定义一个节点结构体,来存储节点的数据以及左右子节点的指针。
```ctypedef struct TreeNode {int data;struct TreeNode left;struct TreeNode right;} TreeNode;```接下来,实现插入节点的函数。
插入节点时,需要按照二叉搜索树的规则,找到合适的位置插入新节点。
```cvoid insertNode(TreeNode root, int value) {if (root == NULL) {root =(TreeNode )malloc(sizeof(TreeNode));(root)>data = value;(root)>left = NULL;(root)>right = NULL;return;}if (value <(root)>data) {insertNode(&((root)>left), value);} else if (value >(root)>data) {insertNode(&((root)>right), value);}}```查找节点的函数也类似,根据比较值的大小在左右子树中递归查找。
数据结构树的逻辑表示方法
数据结构树的逻辑表示方法数据结构树是一种以分层的方式,将数据组织成树形结构的一种数据结构。
它由一个或多个节点组成,每个节点包含一个数据元素和若干指向其他节点的指针。
树的逻辑表示方法主要包括,孩子兄弟表示法、双亲表示法和邻接表表示法。
孩子兄弟表示法是一种常用的表示树的方法。
它通过将每个节点分别表示为一个数据元素和两个指针,分别指向该节点的第一个孩子和该节点的下一个兄弟节点。
这样,可以有效地表示一棵树,且插入和删除节点的操作也相对较为简单。
例如,假设有以下一棵树:A/ \B C/ \D E可以使用孩子兄弟表示法表示为:节点A:数据元素为A,指针1指向节点B,指针2指向节点C。
节点B:数据元素为B,指针1指向节点D,指针2指向节点E。
节点C:数据元素为C,指针1为空,指针2为空。
节点D:数据元素为D,指针1为空,指针2为空。
节点E:数据元素为E,指针1为空,指针2为空。
这样,通过孩子兄弟表示法,我们可以方便地表示并操作这棵树。
双亲表示法是另一种常见的表示树的方法。
它通过定义一个数组,数组的下标表示节点的编号,数组的值表示节点的父节点的编号。
通过这种方式,可以快速地找到一个节点的父节点。
例如,假设有以下一棵树:A(0)/ \B(1) C(2)/ \D(3) E(4)可以使用双亲表示法表示为一个数组:[0, 0, 1, 1, 2]数组的下标表示节点的编号,数组的值表示节点的父节点的编号。
例如,第一个值0表示节点A的父节点是根节点,第二个值0表示节点B的父节点是根节点,以此类推。
通过双亲表示法,可以快速地找到一个节点的父节点,但是找到一个节点的子节点和兄弟节点则相对较为困难。
邻接表表示法是另一种常用的表示树的方法。
它通过使用链表来表示树中的每个节点,并使用一个数组来存储每个节点的指针。
数组的下标表示节点的编号,数组的值表示节点的指针所指向的链表。
例如,假设有以下一棵树:A(0)/ \B(1) C(2)/ \D(3) E(4)可以使用邻接表表示法表示为一个数组和链表:数组:[A, B, C, D, E]链表:[B -> D -> E, C, NULL, NULL, NULL]数组存储着每个节点的指针,链表存储着每个节点的子节点。
树形结构数据库表设计
树形结构数据库表设计转载:逻辑数据库设计 - 单纯的树(递归关系数据)相信有过开发经验的朋友都曾碰到过这样⼀个需求。
假设你正在为⼀个新闻⽹站开发⼀个评论功能,读者可以评论原⽂甚⾄相互回复。
这个需求并不简单,相互回复会导致⽆限多的分⽀,⽆限多的祖先-后代关系。
这是⼀种典型的递归关系数据。
对于这个问题,以下给出⼏个解决⽅案,各位客观可斟酌后选择。
⼀、邻接表:依赖⽗节点 邻接表的⽅案如下(仅仅说明问题): CREATE TABLE Comments( CommentId int PK, ParentId int, --记录⽗节点 ArticleId int, CommentBody nvarchar(500), FOREIGN KEY (ParentId) REFERENCES Comments(CommentId) --⾃连接,主键外键都在⾃⼰表内 FOREIGN KEY (ArticleId) REFERENCES Articles(ArticleId) ) 由于偷懒,所以采⽤了书本中的图了,Bugs就是Articles: 这种设计⽅式就叫做邻接表。
这可能是存储分层结构数据中最普通的⽅案了。
下⾯给出⼀些数据来显⽰⼀下评论表中的分层结构数据。
⽰例表: 图⽚说明存储结构:邻接表的优缺分析 邻接表的优缺分析 对于以上邻接表,很多程序员已经将其当成默认的解决⽅案了,但即便是这样,但它在从前还是有存在的问题的。
分析1:查询⼀个节点的所有后代(求⼦树)怎么查呢? 我们先看看以前查询两层的数据的SQL语句: SELECT c1.*,c2.* FROM Comments c1 LEFT OUTER JOIN Comments2 c2 ON c2.ParentId = mentId 显然,每需要查多⼀层,就需要联结多⼀次表。
SQL查询的联结次数是有限的,因此不能⽆限深的获取所有的后代。
⽽且,这种这样联结,执⾏Count()这样的聚合函数也相当困难。
左右值无限分类实现算法
/viewthread.php?tid=227253一、引言产品分类,多级的树状结构的论坛,邮件列表等许多地方我们都会遇到这样的问题:如何存储多级结构的数据?在PHP的应用中,提供后台数据存储的通常是关系型数据库,它能够保存大量的数据,提供高效的数据检索和更新服务。
然而关系型数据的基本形式是纵横交错的表,是一个平面的结构,如果要将多级树状结构存储在关系型数据库里就需要进行合理的翻译工作。
接下来我会将自己的所见所闻和一些实用的经验和大家探讨一下:层级结构的数据保存在平面的数据库中基本上有两种常用设计方法:* 毗邻目录模式(adjacency list model)* 预排序遍历树算法(modified preorder tree traversal algorithm)我不是计算机专业的,也没有学过什么数据结构的东西,所以这两个名字都是我自己按照字面的意思翻的,如果说错了还请多多指教。
这两个东西听着好像很吓人,其实非常容易理解。
二、模型这里我用一个简单食品目录作为我们的示例数据。
我们的数据结构是这样的,以下是代码:1.Food2.|3.|---Fruit4.| |5.| |---Red6.| | |7.| | |--Cherry8.| |9.| +---Yellow10.| |11.| +--Banana12.|13.+---Meat14. |--Beef15. +--Pork复制代码为了照顾那些英文一塌糊涂的PHP爱好者1.Food : 食物2.Fruit : 水果3.Red : 红色4.Cherry: 樱桃5.Yellow: 黄色6.Banana: 香蕉7.Meat : 肉类8.Beef : 牛肉9.Pork : 猪肉复制代码三、实现1、毗邻目录模式(adjacency list model)这种模式我们经常用到,很多的教程和书中也介绍过。
我们通过给每个节点增加一个属性parent 来表示这个节点的父节点从而将整个树状结构通过平面的表描述出来。
树形结构左右值存储,移动节点详解
树形结构左右值存储,移动节点详解最近做⼀个程序,⽤到树形结构,并且要存储到数据库中。
于是研究了⼀下树形结构的左右值存储。
左右值虽然取⽗祖节点和⼦孙节点,查找节点路径⾮常⽅便,但要找某节点的⽗节点,⼦节点和兄弟节点就⽐较困难,所以还要需要⼀个层级维度⽅便确定⽗⼦和兄弟节点,也就是树形结构中所说的树的深度。
下⾯列举⼀些普通的左右值算法,⽹上有⼤量的资料,就不细说了。
以下资料来⾃⽹上,错误的地⽅我已纠正⼀、计算某节点的⼦孙节点数。
⼦孙节点数量 = (节点右值-节点左值-1)/2⼆、查找某节点的所有⼦孙节点。
select * from tree where L > 节点左值 and R < 节点右值 order by L asc;三、查找某节点的所有⽗祖节点。
select * from tree where L < 节点左值 and R > 节点右值 order by L desc;四、某节点所处层级。
select count(*) from tree where L <= 节点左值 and R >= 节点右值五、增加节点。
需要为要增加的节点腾出左右值的空间。
然后将新节点插⼊数据库。
在哪⾥增加?这就需要参照物,有下⾯四种情况。
1.在A节点下增加⼦节点B,B作为第⼀个⼦节点。
#更新受影响节点update tree set L = L + 2 where L > A节点左值;update tree set R = R + 2 where R > A节点左值;#插⼊新增的节点insert into tree (name, L, R) values('B', A节点左值+1, A节点左值+2);2.在A节点下增加⼦节点B,B作为最后⼀个⼦节点。
#更新受影响节点update tree set L = L + 2 where L >= A节点右值;update tree set R = R + 2 where R >= A节点右值;#插⼊新增的节点insert into tree (name, L, R) values('B', A节点右值, A节点右值+1);3.在A节点后⾯增加节点B, B作为A的兄弟节点。
树形结构
树的例子
A
T1 E
B
C
D
F
G
H
I
J
K
L
T2
M
T3
树结构中的常用术语: 树结构中的常用术语:
结点( 结点 ( Node): 树中的元素 , 包含数据项及若干指向其 ) 树中的元素, 子树的分支。 子树的分支。 结点的度( 结点的度 ( Degree) : 结点拥有的子树数 。 树中最大结 ) 结点拥有的子树数。 点的度数称为树的度数。 点的度数称为树的度数。 结点的层次:从根结点开始算起,根为第一层。 结点的层次:从根结点开始算起,根为第一层。
(4)二叉树的存储结构 (4)二叉树的存储结构 对于二叉树,我们既可采用顺序存储,又可采用 链式存储。 ①顺序存储结构
顺序存储就是将一棵二叉树的所有结点按照一定的次序 顺序存储就是将一棵二叉树的所有结点按照一定的次序 顺序存放到一组连续的存储单元中 为此, 到一组连续的存储单元 顺序存放到一组连续的存储单元中,为此,必须把二叉树中 所有结点构成一个适当的线性序列,以使各个结点在这个序 所有结点构成一个适当的线性序列, 列中的相互位置能反映出结点之间的逻辑关系 相互位置能反映出结点之间的逻辑关系。 列中的相互位置能反映出结点之间的逻辑关系。 对于完全二叉树按结点的编号顺序,就能得到一个足以 对于完全二叉树按结点的编号顺序, 完全二叉树按结点的编号顺序 反映整个二叉树结构的线性序列。因此, 反映整个二叉树结构的线性序列。因此,可将完全二叉树中 所有结点按编号顺序依次存储到一组连续的存储单元(即向量 即向量) 所有结点按编号顺序依次存储到一组连续的存储单元 即向量 这样既不浪费内存, 中,这样既不浪费内存,又可以利用地址公式确定其结点的 位置。但对于一般的二叉树,顺序分配常会造成内存的浪费, 位置。但对于一般的二叉树,顺序分配常会造成内存的浪费, 因为一般的二叉树也必须按完全二叉树的形式来存储 一般的二叉树也必须按完全二叉树的形式来存储。 因为一般的二叉树也必须按完全二叉树的形式来存储。
关于树状结构数据的一些常用处理,比如找所有父级和子级,一维数组转无限级树状结构
关于树状结构数据的⼀些常⽤处理,⽐如找所有⽗级和⼦级,⼀维数组转⽆限级树状结构树状结构数据在⽇常开发是最经常遇到的数据,⽐如⼀些后台管理系统左侧菜单就是⼀个树状结构的数据,这些数据的特点有,可以⽆限的⼦节点,⽗级与⼦级⼀般会存在上级关系,⽐如⼦级的属性会有⽗级的唯⼀标识id,我这⾥总结了,⼀维数组转⽆限级树状结构,树状结构转⼀维数组,根据指定属性值找所有的⽗级或者⼦级数据,有不对的地⽅,还望⼤佬们多多指点.⼀、⼀维数组转⽆限级树状结构1、使⽤递归法:数据会存在上下级关系,否则⽆法转换1 let data1 = [2 {id:1,pid:0,name:"name1"},3 {id:2,pid:0,name:"name2"},4 {id:3,pid:2,name:"name3"},5 {id:4,pid:1,name:"name4"},6 {id:5,pid:4,name:"name5"},7 {id:6,pid:5,name:"name6"},8 {id:7,pid:5,name:"name6"},9 {id:8,pid:7,name:"name6"},1011 ]12//递归法:⼀维数组转⽆限极树状结构13/**14 *15 * @param data 数据源,⼀维数据16 * @param idKeys 要匹配所在项的唯⼀idkey 属性字段,⽐如idkeys ='id',17 * @param pidKeys 要匹配所在项的上级 pidkey 属性字段,⽐如pidkeys = 'pid',18 * @param pid 要匹配所在项⽬的上级pidkey 字段的值,⽐如 pid = 019 * @param leve 当前所在树状结构的层级数20*/21 export function oneToTree<T extends {[key:string]:any}>(data:T[],idKeys?:string,pidKeys?:string,pid?:any,leve?:number){22 let idKey = idKeys||"id"23 let pidKey = pidKeys||'pid'24 let leveKey = "$leve"25 let childrenKey = "children"26 let pidData = pid||027 let leves = leve||1;28if(!Array.isArray(data)) return data;29 type resI = T&{$leve:number,children:resI[]};//使⽤交叉类型,新增了两个字段$live,children30 let resArr:Array<resI> =[];31 data.forEach( (itme:any)=> {32if (itme[pidKey] === pidData) {33 itme[leveKey] = leves;34 itme[childrenKey] = oneToTree(data, idKey, pidKey, itme[idKey], leves + 1);35 resArr.push(itme)36 }37 })3839return resArr4041 }42 let data1 = oneToTree(data1)⼆、数状结构数据转⼀维数组1/**2 * @param data 数据源(数状结构)3 * @param childrenKeys : 每项的⼦级字段key,默认为:children4*/5 export function treeToOneArr<T extends {[key:string]:any}>(data:T[],childrenKey?:string):T[]{6 let resArr:T[] = [];7 childrenKey = childrenKey||'children'8for(let i=0;i<data.length;i++){9 let itme:any = data[i];// 这⾥有点不好,⽤了any 类型,返回数据的成员掉失了类型检测,10if(Array.isArray(itme[childrenKey])){11 let child:T[] = itme[childrenKey];12 itme[childrenKey] = [];13 resArr = resArr.concat(itme).concat(treeToOneArr(child,childrenKey))14 }else{15 resArr.push(itme)16 }17 }1819return resArr20 }2122 console.log(treeToOneArr(data4));三、⼀维数组,找所有上级或者下级指定数据(1. ) :每项之间依赖字段存在上下层关系(2. ):给出指定字段的值找出当前项所有的下级/上级,匹配项的指定字段的值或者匹配的所有项let data1 = [{id:1,pid:0,name:"name1"},{id:2,pid:0,name:"name2"},{id:3,pid:2,name:"name3"},{id:4,pid:1,name:"name4"},{id:5,pid:4,name:"name5"},{id:6,pid:5,name:"name6"},{id:7,pid:5,name:"name6"},{id:8,pid:7,name:"name6"},]/*** ⼀维数组,每项之间依赖字段存在上下层关系,根据依赖项字段,给出指定字段的值找出当前项所有的下级/上级指定字段/所有项* @param data ,数据源,⼀维数组* @param value 给出要与依赖字段(PidKeys) 匹配的值* @param idKeys 所在项的唯⼀key ,也是作为下级的依赖字段,默认为id,⽐如:id,pid* @param pidKeys 要与指定value 匹配的字段(不是值,是字段key),也是所在项的依赖字段,默认为pid,⽐如,id,pid* @param reKeys 要返回的指定字段值,默认为和idkeys⼀样的* @param field 是否要返回匹配项的所有字段,默认为false*//*1. 找所有上级:把每项的存在依赖关系的字段(如pid)作为匹配字段(idkeys),依赖字段作为为匹配字段2. 找所有下级:和上级刚好相反*/export function findTreenField<T extends {[key:string]:any}>(data:T[],value:any,idKeys?:string,pidKeys?:string,reKeys?:string,field?:boolean){let idKey = idKeys||"id"let pidKey = pidKeys||"pid"let reKey = reKeys||idKey;let fields = field||falseif(!value ||value===0) return [];if(!Array.isArray(data)) return [];var resArr:any[] = [];for (let i = 0; i < data.length; i++) {let itme:T = data[i];if(itme[pidKey]===value){if(fields){resArr.push(itme);}else{resArr.push(itme[reKey]);}resArr = resArr.concat(findTreenField(data, itme[idKey],idKey, pidKey, reKey,fields))}}return resArr}// 找所有⼦级console.log(findTreenField(data1,5)) ;//[6, 7, 8]//找所有⽗级console.log(findTreenField(data1,5,"pid","id")) ;//[4,1,0]四、树状结构数据,根据指定值找所有上级节点(只需要知道⼦节点的属性key)1. 递归法2. 思路:先递归数组往下找,根据当前属性keys的值如果和value 相等,找到要匹配当前value 所在的项,退出当前循环,把当前的项的属性kesy对应的值作为value 参数,递归循环,⼀层层往上找1 const data = [2 {id:1,children:[3 {id:1.1,children:null},4 {id:1.2,children:null},5 {id:1.3,children:[6 {id:1.31,children:null},7 {id:1.32,children:[8 {id:1.321,children:[9 {id:1.3211,children:null}10 ]},1516 {id:2,children:[17 {id:2.1,children:[18 {id:2.11,children:null},19 {id:2.12,children:[20 {id:2.121,children:null}21 ]},22 {id:2.13,children:null},23 ]},24 ]},25 ]2627/**28 *29 * @param dataArr 数据源(数状结构tree)30 * @param value 要匹配的值31 * @param keys 与value 匹配的属性keys ,⽐如'id' ,'index' 对象的值32 * @param rekeys 要返回的属性 reskeys,⽐如'id' ,'index' 对应的值33 * @param childrenKeys ⼦节点的属性,默认 children34*/35 export function findTreeParendId<T extends {[key:string]:any}>(dataArr:T[],value:any,keys:string,rekeys:string,childrenKeys?:string):Array<keyof T>{36 let data = JSON.parse(JSON.stringify(dataArr));//避免引⽤,做深拷贝处理37var resArr:Array<keyof T> = [];38 let childrenKey = childrenKeys||'children';39if(data.length<0){40return resArr41 }42 let recursion = (arrs:T[],itmeId:any,parendId?:any)=>{43for(let i=0;i<arrs.length;i++){4445 let itme:T = arrs[i]46if(itme[keys]===itmeId){47 resArr.unshift(itme[rekeys]);// 找到匹配的就加进去48if(parendId){49 recursion(data,parendId)50 }51break;//跳出当前循环52 }else{53//找不到,如果有⼦级,递归往下找54if(itme[childrenKey]&& Array.isArray(itme[childrenKey])){55 recursion(itme[childrenKey],itmeId,itme[keys])56 }57 }58 }59 }60 recursion(data,value)61return resArr;62 }63 console.log(findTreeParendId(data,2.121,"id","id"));//[2, 2.1, 2.12, 2.121]五、树状结构数据,根据指定值找所有下级节点(只需要知道⼦节点的属性key)1、使⽤递归法2、实现思路和第四个找所有⽗级节点是⼀样,但是实现有点不同(有更好的实现⽅法可以留⾔)1 const data = [2 {id:1,children:[3 {id:1.1,children:null},4 {id:1.2,children:null},5 {id:1.3,children:[6 {id:1.31,children:null},7 {id:1.32,children:[8 {id:1.321,children:[9 {id:1.3211,children:null}10 ]},11 {id:1.322,children:null}12 ]}13 ]},14 ]},1516 {id:2,children:[17 {id:2.1,children:[18 {id:2.11,children:null},19 {id:2.12,children:[20 {id:2.121,children:null}21 ]},2627/**28 *29 * @param data 数据源(数状结构tree)30 * @param value 给出指定要匹配的值⽐ 131 * @param idkeys 被匹配的字段属性 ,⽐如:id(默认)32 * @param reKeys 要返回的字段属性,⽐如 id(默认)33 * @param childrenKeys 指定每项的⼦级字段,⽐如:children(默认)34*/35 export function findChildFiled<T extends {[key:string]:any}>(data:T[],value:any,idkeys?:string,reKeys?:string,childrenKeys?:string){36 let idkey = idkeys||'id';37 let reKey = reKeys||'id';38 let childrenKey = childrenKeys||'children'39 let arrRes:any[] = [];40//2.对匹配的所在项,进⾏递归获取所有⼦项的指定字段值41 let findReKeys = function(arr:T[]){42if(!Array.isArray(arr)) return arr;43for(let i =0;i<arr.length;i++){44 let itme:T = arr[i];45 arrRes.push(itme[reKey])46 findReKeys(itme[childrenKey])47 }48 }49//1.先递归找到指定值的所在项50 let findNode = function(arr:T[]){51if(!Array.isArray(arr)) return arr;52for(let i =0;i<arr.length;i++){53 let itme:T = arr[i];54if(itme[idkey]===value){55 findReKeys([itme])56break;57 }else{58 findNode(itme[childrenKey])59 }6061 }62 }63 findNode(data)64return arrRes65 }66 console.log(findChildFiled(data,1.3));//[1.3, 1.31, 1.32, 1.321, 1.3211, 1.322]67 console.log(findChildFiled(data,2.1));//[2.1, 2.11, 2.12, 2.121, 2.13]。
树形结构版本
树形结构版本
树形结构(Tree Structure)是一种层次化的数据结构,由节点(Node)和边(Edge)组成。
每个节点可以有一个或多个子
节点,但只能有一个父节点,同时树形结构没有环状连接。
树状结构中的一个节点被称为根节点,没有父节点的节点被称为叶节点(Leaf Node),其他节点被称为内部节点(Internal Node)。
树形结构可以用于模拟现实世界中的层次关系,比如文件系统中的目录结构、组织架构图等。
在计算机科学中,树形结构被广泛应用于数据存储、算法设计等领域。
常见的树形结构包括二叉树、红黑树、AVL树等。
二叉树是一种特殊的树形结构,每个节点最多只有两个子节点,分别称为左子节点和右子节点。
二叉树可以用于实现搜索树、堆栈等数据结构,常见的操作包括插入节点、删除节点、搜索节点等。
红黑树是一种自平衡二叉搜索树,通过保持树的平衡性,可以保证查找、插入、删除等操作的时间复杂度为O(log n)。
红黑
树的特点是每个节点都有颜色(红色或黑色),并满足以下几个性质:
1. 每个节点要么是红色,要么是黑色
2. 根节点是黑色
3. 所有叶节点(NIL)是黑色
4. 如果一个节点是红色,则其子节点都是黑色
5. 从任意节点到其每个叶子节点的路径都包含相同数目的黑色
节点
AVL树是一种自平衡二叉搜索树,通过调整树的高度和节点的平衡因子来保持树的平衡。
AVL树在插入和删除节点时需要进行平衡操作,使得任意节点的左右子树高度差不超过1。
由于平衡操作的必要性,AVL树的操作效率比红黑树略低,但在特定场景下可以提供更好的性能保证。
[TREE]采用左右值编码来存储无限分级树形结构的数据库表设计
BEGIN TRANSACTION select @lft=lft,@rgt=rgt,@layer=layer from TreeView where type_id=@type_id if exists (select * from TreeView where rgt=@lft-1 and layer=@layer)
1 商品 18
+-----------------------------------+
14-12 电器 17-12
2 +4 食品 13+4
15-12 电冰箱 16-12
+----------------------+ 3+4 肉类 8+4 9 +4 蔬菜类 12+4
+-------------------+ 4 +4 猪肉 5+4 6 +4 牛肉 7+4 10+4 白菜 11+4
1 商品 18
+- -- -- --- -- -- -- -- --- -- -- -- -- --- -- -- -- --- +
2 食品 11
12 电器 17
+-----------------+
3 肉类 6
7 蔬菜类 10
+---------------------+ 13 电视机 14 15 电冰箱 16
End
注意:因为删除某个节点会同时删除该节点的所有子孙节点,而这些被删除的节点的个数为:(被删节 点的右值-被删节点的左值+1)/2,而任何一个节点同时具有唯一的左值和唯一的右值,故删除作废节点后, 其他相应节点的左、右值需要调整的幅度应为:减少(被删节点的右值-被删节点的左值 +1)。
【MySQL疑难杂症】如何将树形结构存储在数据库中(方案二PathEnumeration)
【MySQL疑难杂症】如何将树形结构存储在数据库中(⽅案⼆PathEnumeration) 今天来介绍把树形结构存⼊数据库的第⼆种⽅法——路径枚举法。
还是借⽤上⼀篇的栗⼦,为了⽅便⼤家查阅,我把图⼜原样搬过来了。
需要回答的问题依旧是这样⼏个: 1.查询⼩天的直接上司。
2.查询⽼宋管理下的直属员⼯。
3.查询⼩天的所有上司。
4.查询⽼王管理的所有员⼯。
⽅案⼆、 Path Enumeration 路径枚举法,记录下根节点到每个⼦节点的路径。
先创建表:CREATE TABLE employees2(eid INT,ename VARCHAR(100),position VARCHAR(100),path VARCHAR(200)) 然后插⼊数据: 现在我们来回答⼀下之前的问题: 1.查询⼩天的直接上司。
在上⼀个解决⽅案中能轻⽽易举做到的事情,在这个⽅案中却有些⿇烦了,因为需要对path字段进⾏字符串处理,去掉“/”+⾃⾝id才是直接上司的path值。
⼜开始⼀顿骚操作: SELECT e1.eid,e1.ename FROM employees2 e1,employees2 e2 WHERE e2.ename = '⼩天' AND e1.path = REPLACE(e2.path,CONCAT('/',e2.eid),''); 好像这个操作还不够sao,2333,结果如下: 2.查询⽼宋管理下的直属员⼯。
怎么查管理下的直属员⼯呢?那就要⽤模糊查询了: SELECT e2.eid,e2.ename FROM employees2 e1,employees2 e2 WHERE e1.ename = '⽼宋' AND e2.path REGEXP CONCAT(e1.path,'/[0-9]{1,}$'); 这⾥⽤了正则匹配,匹配所有path符合规则的记录,结果如下: 3.查询⼩天的所有上司。
mysql-BTree和B+Tree详解
mysql-BTree和B+Tree详解常⻅的数组、链表、栈和队列都是线性结构,在存储⼤量数据时访问速度⽐较慢,⽽树(tree)则是⼀种⾮线性结构,使得访问时间复杂度降低到O(logn)。
下图是使⽤树结构存储的集合 {A,B,C,D,E,F,G,H,I,J,K,L,M,P,Q} 的⽰意图。
对于数据 A 来说,和数据 B、C、D 有关系;对于数据 B 来说,和 E、F 有关系。
这就是“⼀对多”的关系。
将具有“⼀对多”关系的集合中的数据元素按照图下图的形式进⾏存储,整个存储形状在逻辑结构上看,类似于实际⽣活中倒着的树,所以称这种存储结构为“树型”存储结构。
⼀些概念结点:树中⼀个最基本的数据称为⼀个节点(node) A-Q根节点:以某个节点为⼀个根节点,从这个节点往下形成⼀棵⼦树(sub-tree)。
A叶⼦结点:整棵树最年轻的⼀代,在树中称为树的叶节点(leaf node)。
通俗就是没有孩⼦的结点 B C H I K L M N P Q每个⽗辈有⼏个孩⼦,每个孩⼦有其⽗辈,还有其祖辈。
⽗辈称为⽗节点(parent),孩⼦称为⼦节点(child)。
每个⽗节点可以对应⼀个或者多个⼦节点,⼀个⼦节点只能有⼀个⽗节点。
具有相同⽗节点的节点称为兄弟节点(siblings)。
结点的层次:根节点为第⼀层,根节点的孩⼦结点为第2层,依次类推深度:树的层次的最⼤值结点的度:结点拥有⼦树的数⽬⼆叉树:每个结点最多有2颗⼦树,简单地理解,满⾜以下两个条件的树就是⼆叉树:1. 本⾝是有序树;2. 树中包含的各个节点的度不能超过 2,即只能是 0、1 或者 2;tree满⼆叉树:除了叶⼦结点,每个节点都有2个孩⼦结点,且叶⼦节点在同⼀层上完全⼆叉树:从树的根节点,从上⾄下,从左到右,依次填满结点形成的⼆叉树,满⼆叉树是⼀颗完全⼆叉树⼆叉树的搜索速度取决于树的层级B TREE(B-TREE)也就是平衡多路搜索树,也就是多叉的意思当数据库⾥单表存⻋处的数值可能是百万级或者千万级,如果有⼆叉树存储就会需要百万个千万个树节点,有没有办法让⼀个节点存储多个元素(数值),那么,是不是可以减少树节点,从⽽减少树整体层级⾼度呢,要知道,减少树层级⾼度的过程,就是优化查询效率的过程。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
采用左右值编码来存储无限分级树形结构的数据库表设计之前我介绍过一种按位数编码保存树形结构数据的表设计方法,详情见:浅谈数据库设计技巧(上)该设计方案的优点是:只用一条查询语句即可得到某个根节点及其所有子孙节点的先序遍历。
由于消除了递归,在数据记录量较大时,可以大大提高列表效率。
但是,这种编码方案由于层信息位数的限制,限制了每层能所允许的最大子节点数量及最大层数。
同时,在添加新节点的时候必须先计算新节点的位置是否超过最大限制。
上面的设计方案必须预先设定类别树的最大层数以及最大子节点数,不是无限分级,在某些场合并不能采用,那么还有更完美的解决方案吗?通过google的搜索,我又探索到一种全新的无递归查询,无限分级的编码方案——左右值。
原文的程序代码是用php写的,但是通过仔细阅读其数据库表设计说明及相关的sql语句,我彻底弄懂了这种巧妙的设计思路,并在这种设计中新增了删除节点,同层平移的需求(原文只提供了列表及插入子节点的sql语句)。
下面我力图用比较简短的文字,少量图表,及相关核心sql语句来描述这种设计方案:首先,我们弄一棵树作为例子:商品|---食品| |---肉类| | |--猪肉| |---蔬菜类| |--白菜|---电器|--电视机|--电冰箱select count(*) from tree where lft <= 2 and rgt >= 11为了方便列表,我们可以为tree表建立一个视图,添加一个层数列,该类别的层数可以写一个自定义函数来计算。
该函数如下:CREATE FUNCTION dbo.CountLayer(@type_id int)RETURNS intASbegindeclare@result intset@result=0declare@lft intdeclare@rgt intif exists (select1from tree where type_id=@type_id)beginselect@lft=lft,@rgt=rgt from tree where type_id=@type_idselect@result=count(*) from tree where lft <=@lft and rgt >=@rgtendreturn@resultendGO然后,我们建立如下视图:CREATE VIEW dbo.TreeViewASSELECT type_id, name, lft, rgt, dbo.CountLayer(type_id) AS layer FROM dbo.tree ORDE R BY lftGO()AS declare declare ifgo假定我们要在节点“肉类”下添加一个子节点“牛肉”,该树将变成:1商品18+2+--------------------------------------------+2食品11+2 12+2电器17+2+-----------------+ +-------------------------+3肉类6+2 7+2蔬菜类10+2 13+2电视机14+2 15+2电冰箱16+2 +-------------+4猪肉5 6牛肉78+2白菜9+2看完上图相应节点左右值的变化后,相信大家都知道该如何写相应的sql脚本吧?下面我给出相对完整的插入子节点的存储过程:CREATE PROCEDURE[dbo].[AddSubNodeByNode](@type_id int,@name varchar(50))ASdeclare@rgt intif exists (select1from tree where type_id=@type_id)beginSET XACT_ABORT ONBEGIN TRANSACTIONselect@rgt=rgt from tree where type_id=@type_idupdate tree set rgt=rgt+2where rgt>=@rgtupdate tree set lft=lft+2where lft>=@rgtinsert into tree (name,lft,rgt) values (@name,@rgt,@rgt+1)COMMIT TRANSACTIONSET XACT_ABORT OFFend然后,我们删除节点“电视机”,再来看看该树会变成什么情况:1商品20-2+-----------------------------------+2食品13 14电器19-2+-----------------+3肉类8 9蔬菜类12 17-2电冰箱18-2+----------+4猪肉5 6牛肉7 10白菜11相应的存储过程如下:CREATE PROCEDURE[dbo].[DelNode]@type_id intASdeclare@lft intdeclare@rgt intif exists (select1from tree where type_id=@type_id)beginSET XACT_ABORT ONBEGIN TRANSACTIONselect@lft=lft,@rgt=rgt from tree where type_id=@type_iddelete from tree where lft>=@lft and rgt<=@rgtupdate tree set lft=lft-(@rgt-@lft+1) where lft>@lftupdate tree set rgt=rgt-(@rgt-@lft+1) where rgt>@rgtCOMMIT TRANSACTIONSET XACT_ABORT OFFEnd注意:因为删除某个节点会同时删除该节点的所有子孙节点,而这些被删除的节点的个数为:(被删节点的右值-被删节点的左值+1)/2,而任何一个节点同时具有唯一的左值和唯一的右值,故删除作废节点后,其他相应节点的左、右值需要调整的幅度应为:减少(被删节点的右值-被删节点的左值+1)。
最后,让我们看看平移节点“电器”,将其和其所有子孙节点移动到节点“食品”之前后,该树会变成什么情况:1商品18+-----------------------------------+14-12电器17-12 2+4食品13+4+----------------------+ 15-12电冰箱16-12 3+4肉类8+4 9+4蔬菜类12+4+-------------------+4+4猪肉5+4 6+4牛肉7+4 10+4白菜11+4大家仔细观察一下交换后同层2个节点和其所有子孙节点左右值的变化,可以发现一个明显的规律,那就是,节点“电器”及其所有子孙节点的左右值均减少12,而节点“食品”及其所有子孙节点的左右值均增加4。
而节点“电器”+其子孙节点的数量为2,节点“食品”+其子孙节点的数量为6,这其中有什么联系吗?还记得我在删除节点的存储过程后面的注释吗?任何一个节点同时具有唯一的左值和唯一的右值。
让我们把节点数量*2,正好和节点左右值需要调整的幅度相等。
由此规律,我们可以编写出类似下面的存储过程来实现节点同层前移的功能:CREATE PROCEDURE[dbo].[MoveNodeUp]@type_id intASdeclare@lft intdeclare@rgt intdeclare@layer intif exists (select1from tree where type_id=@type_id)beginSET XACT_ABORT ONBEGIN TRANSACTIONselect@lft=lft,@rgt=rgt,@layer=layer from TreeView where type_id=@type_idif exists (select*from TreeView where rgt=@lft-1and layer=@layer)begindeclare@brother_lft intdeclare@brother_rgt intselect@brother_lft=lft,@brother_rgt=rgt from TreeView where rgt=@lft-1an d layer=@layerupdate tree set lft=lft-(@brother_rgt-@brother_lft+1) where lft>=@lft and rgt <=@rgtupdate tree set lft=lft+(@rgt-@lft+1) where lft>=@brother_lft and rgt<=@br other_rgtupdate tree set rgt=rgt-(@brother_rgt-@brother_lft+1) where rgt>@brother_ rgt and rgt<=@rgtupdate tree set rgt=rgt+(@rgt-@lft+1) where lft>=@brother_lft+(@rgt-@lft+ 1) and rgt<=@brother_rgtendCOMMIT TRANSACTIONSET XACT_ABORT OFFend注意:节点的同层平移可以采用临时表来做中介,降低代码的复杂度。
不用临时表来处理也行,但是update语句顺序一定要考虑周详。
否则,一旦出现bug,对整个类别表的破坏是惊人的,强烈推荐在做上述工作前对类别表进行完整备份。
同层下移的存储过程和同层上移类似,有兴趣的朋友可以自己动手编写体味一下其中的细节,我就不在这里列出来了。
最后,我对上面这种左右值编码实现无限分级类别树的方案做一个总结:优点:在消除递归的前提下实现了无限分级,而且查询条件是基于整形数字比较的,效率很高。
可以进行先序列表,添加,修改,删除,同层平移等常规操作,基本满足需求。
缺点:由于这种左右值编码的方式和常见的阿拉伯数字直观排序不同,再加上节点在树中的层次,顺序不是直观显示出来,而必须通过简单的公式计算后得到,需要花费一定的时间对其数学模型进行深入理解。
而且,采用该方案编写相关存储过程,新增,删除,同层平移节点需要对整个树进行查询修改,由此导致的代码复杂度,耦合度较高,修改维护的风险较高。
发表于@ 2007年04月26日16:36:00|评论(4 )|编辑新一篇: 自定义MembershipProvider来利用 2.0 Login控件的登陆和修改密码模块 | 旧一篇: 分页存储过程的一点心得老板心里你有多重?确立职场位置,明确自身重要性Leo教你看清前途老板心里你有多重?确立职场位置,明确自身重要性Leo教你看清前途评论#hometohome 发表于2008-05-07 22:17:19 IP: 123.115.4.*请问该如何根据子节点的type_id找出父节点呢?比如:猪肉的父节点是肉类、食品、商品#hometohome 发表于2008-05-07 22:38:54 IP: 123.115.4.*declare @rgt intselect @rgt=rgt from tree where type_id=13select * from tree where rgt>=@rgt and lft<=@rgt order by lft我是楼上的。