树形结构数据存储方案(五):区间嵌套

2017 年 9 月 3 日 数据库开发

(点击上方公众号,可快速关注)


来源:标点符

www.biaodianfu.com/nested-intervals.html

如有好文章投稿,请点击 → 这里了解详情


前面的一篇文章介绍了左右值编码,不知道大家注意到了没有,如果数据庞大,每次更新都需要更新差不多全表,效率较低没有更好的方式?今天我们就来研究下区间嵌套法。


区间嵌套法原理


如果节点区间[clft, crgt]与[plft, prgt]存在如下关系:plft <= clft and crgt >= prgt,则[clft, crgt]区间里的点是[plft, prgt]的子节点。基于此假设我们就可以通过对区间的不断的向下划来获取新的区间。举例:如果在区间[plft, prgt]中存在一个空白区间[lft1, rgt1],如果要加入一个[plft,lft1]、[rgt1,prgt]同级的区间,只需插入节点:[(2*lft1+rgt1)/3,  (rgt1+2*lft)/3]。在添加完节点后我们还留下[lft1,(2*lft1+rgt1)/3]和 [(rgt1+2*lft)/3,rgt1]两个空余的空间用来添加更多的子节点。



如果我们把区间放在二位平面上,把rgt当成是x轴,lft当做是y轴,纳闷嵌套的区间数差不多是这样的:



每个节点[lft, rgt]拥有的子节点都被包含在y >= lft & x <= rgt中。同时y >= clft & x <= crgt所在的空间也是y >= plft  & x <= prgt的子集。另外由于新增的右区间都小于已有的左区间,所以新增的节点均在y=x这条直线以下。


区间嵌套法实现


了解了区间嵌套法的原理后,接下来我们就要考虑如何实现他,原则上初始的区间使用任何区间都是可以的,这里我们使用的是[0,1]作为根区间。



首先,我们在XY平面上定义2个点。深度优先集合点和广度有限集合点,针对点<x=1,y=1/2>的深度优先集合点为<x=1,y=1>,广度优先集合点为<x=1/2,y=1/2>。接下来我们定义第一个子节点的位置为父节点和深度优先集合点的中间点。兄弟节点则为前一个子节点到广度优先集合点的中间点,如上图所示,节点1.2的位置为<x=3/4, y=5/8>。


另外仔细看我们可以看到点与点之间的关系。另外如果我们知道x和y的和,我们就能反推出x,y的值(具体的逻辑是什么,我现在也还没有搞懂,有知道的朋友可以帮忙解释下)。


我们以节点<x=3/4, y=5/8>为例,我们可以得到他的和为11/8。


我们定义11为分子(numerator),8为分母(denominator),则x点的分子为:


CREATE DEFINER = `root`@`localhost` FUNCTION `x_numer`(`numer` int,`denom` int)

RETURNS int(11)

BEGIN

DECLARE ret_num INT;

DECLARE ret_den INT;

SET ret_num := numer+1;

SET ret_den := denom*2;

WHILE floor(ret_num/2) = ret_num/2 DO

SET ret_num := ret_num/2;

SET ret_den := ret_den/2;

END WHILE;

RETURN ret_num;

END;


x点的分母为:


CREATE DEFINER = `root`@`localhost` FUNCTION `x_ denom`(`numer` int,`denom` int)

RETURNS int(11)

BEGIN

DECLARE ret_num INT;

DECLARE ret_den INT;

SET ret_num := numer+1;

SET ret_den := denom*2;

WHILE floor(ret_num/2) = ret_num/2 DO

SET ret_num := ret_num/2;

SET ret_den := ret_den/2;

END WHILE;

RETURN ret_den;

END;


Y点的分子:


CREATE DEFINER = `root`@`localhost` FUNCTION `y_numer`(`numer` int,`denom` int)

RETURNS int(11)

BEGIN

DECLARE num INT;

DECLARE den INT;

SET num := x_numer(numer, denom);

SET den := x_denom(numer, denom);

WHILE den < denom DO

SET num := num*2;

SET den := den*2;

END WHILE;

SET num := (numer - num);

WHILE floor(num/2) = num/2 DO

SET num := num/2;

SET den := den/2;

END WHILE;

RETURN num;

END;


Y 的分母:


CREATE DEFINER = `root`@`localhost` FUNCTION `y_denom`(`numer` int,`denom` int)

RETURNS int(11)

BEGIN

DECLARE num INT;

DECLARE den INT;

SET num := x_numer(numer, denom);

SET den := x_denom(numer, denom);

WHILE den < denom DO

SET num := num*2;

SET den := den*2;

END WHILE;

SET num := (numer - num);

WHILE floor(num/2) = num/2 DO

SET num := num/2;

SET den := den/2;

END WHILE;

RETURN den;

END;


接下来我们来测试下,X与Y是否能解码出来:


SELECT

CONCAT(x_numer (11, 8),'/',x_denom (11, 8)) AS X,

CONCAT(y_numer (11, 8),'/',y_denom (11, 8)) AS Y



结果与节点1.2的位置为<x=3/4, y=5/8>完全一致。现在我们知道只需要一个分数即可表示平面上的一个点。


如有已经有分数11/8如何获取该节点的父节点?(如果分子为3,则代表其为根节点)


CREATE DEFINER = `root`@`localhost` FUNCTION `parent_numer`(`numer` int,`denom` int)

RETURNS int(11)

BEGIN

DECLARE ret_num INT;

DECLARE ret_den INT;

IF numer=3 THEN

RETURN denom/2;

END IF;

SET ret_num := (numer-1)/2;

SET ret_den := denom/2;

WHILE floor((ret_num-1)/4) = (ret_num-1)/4 DO

SET ret_num := (ret_num+1)/2;

SET ret_den := ret_den/2;

END WHILE;

RETURN ret_num;

END;


SELECT CONCAT(parent_numer(11,8),'/',parent_denom(11,8)) AS parent


计算当前节点在同级所在的位置:


CREATE DEFINER = `root`@`localhost` FUNCTION `parent_denom`(`numer` int,`denom` int)

RETURNS int(11)

BEGIN

DECLARE ret_num INT;

DECLARE ret_den INT;

IF numer=3 THEN

RETURN NULL;

END IF;

SET ret_num := (numer-1)/2;

SET ret_den := denom/2;

WHILE floor((ret_num-1)/4) = (ret_num-1)/4 DO

SET ret_num := (ret_num+1)/2;

SET ret_den := ret_den/2;

END WHILE;

RETURN ret_den;

END;


有了查询父节点的方法及当前节点所在同级中的位置的方法,即可通过这两个的组合,将节点的路径给计算出来。


CREATE DEFINER = `root`@`localhost` FUNCTION `path`(`numer` int,`denom` int)

RETURNS varchar(255)

BEGIN

IF numer is NULL THEN

RETURN '';

END IF;

RETURN path(parent_numer(numer, denom),parent_denom(numer, denom))|| . || sibling_number(numer, denom);

END;


按照以上方法添加后进行测试,返回[Err] 1424 – Recursive stored functions and triggers are not allowed. 即MySQL的自定义函数不支持递归查询。


CREATE DEFINER = `root`@`localhost` FUNCTION `path`(`numer` int,`denom` int)

RETURNS varchar(255)

BEGIN

DECLARE numer_temp INT;

DECLARE denom_temp INT;

DECLARE path_result VARCHAR(255);

DECLARE path_temp VARCHAR(255);

DECLARE sn VARCHAR(255);

SET path_temp := '';

WHILE numer IS NOT NULL DO

IF path_temp = ''

THEN

SET path_result := sibling_number(numer, denom);

ELSE

SET path_result := CONCAT(sibling_number(numer, denom),'.',path_temp);

END IF;

SET path_temp := path_result;

SET numer_temp := parent_numer(numer, denom);

SET denom_temp := parent_denom(numer, denom);

SET numer := numer_temp;

SET denom := denom_temp;

END WHILE;

RETURN path_result;

END;


SELECT path (11, 8) 的结果为 1.2


计算节点层级的方法:


CREATE DEFINER = `root`@`localhost` FUNCTION `node_level`(`numer` int,`denom` int)

RETURNS int(11)

BEGIN

DECLARE ret_num INT;

DECLARE ret_den INT;

DECLARE ret INT;

SET ret =1;

IF numer=3 THEN

return 1;

END IF;

WHILE numer!=3 DO

SET ret_num := parent_numer(numer, denom);

SET ret_den := parent_denom(numer, denom);

SET numer := ret_num;

SET denom := ret_den;

SET ret := ret + 1;

END WHILE;

RETURN ret;

END;


我们知道了如何将编码过的节点转成目录形式,如何逆转呢?以下是方法:


先添加2个辅助函数:


CREATE DEFINER = `root`@`localhost` FUNCTION `child_numer`(`num` int,`den` int,`child` int)

RETURNS int(11)

BEGIN

RETURN num * power(2, child) + 3 - power(2, child);

END;


CREATE DEFINER = `root`@`localhost` FUNCTION `child_denom`(`num` int,`den` int,`child` int)

RETURNS int(11)

BEGIN

RETURN den*power(2, child);

END;


再来编写逆转函数:


CREATE DEFINER = `root`@`localhost` FUNCTION `path_numer`(`path` varchar(255))

RETURNS int(11)

BEGIN

DECLARE num INT;

DECLARE den INT;

DECLARE postfix VARCHAR(255);

DECLARE sibling VARCHAR(255);

SET num := 1;

SET den := 1;

SET postfix := CONCAT(path,'.');

WHILE length(postfix) > 1 DO

SET sibling := SUBSTR(postfix, 1, instr(postfix,'.')-1);

SET postfix := SUBSTR(postfix, instr(postfix,'.')+1);

SET num := child_numer(num,den,sibling+0);

SET den := child_denom(num,den,sibling+0);

END WHILE;

RETURN num;

END;


CREATE DEFINER = `root`@`localhost` FUNCTION `path_denom`(`path` varchar(255))

RETURNS int(11)

BEGIN

DECLARE num INT;

DECLARE den INT;

DECLARE postfix VARCHAR(255);

DECLARE sibling VARCHAR(255);

SET num := 1;

SET den := 1;

SET postfix := CONCAT(path,'.');

WHILE length(postfix) > 1 DO

SET sibling := SUBSTR(postfix, 1, instr(postfix,'.')-1);

SET postfix := SUBSTR(postfix, instr(postfix,'.')+1);

SET num := child_numer(num,den,sibling+0);

SET den := child_denom(num,den,sibling+0);

END WHILE;

RETURN den;

END;


select CONCAT(path_numer(‘2.1.3′),’/’,path_denom(‘2.1.3’)) 结果为51/64


参考资料:


  • http://www.rampant-books.com/art_vadim_nested_sets_sql_trees.htm


系列回顾




看完本文有收获?请转发分享给更多人

关注「数据库开发」,提升 DB 技能

登录查看更多
1

相关内容

专知会员服务
42+阅读 · 2020年7月7日
【人大】图实现算法综述与评测分析
专知会员服务
37+阅读 · 2020年4月28日
【图神经网络(GNN)结构化数据分析】
专知会员服务
114+阅读 · 2020年3月22日
自动结构变分推理,Automatic structured variational inference
专知会员服务
38+阅读 · 2020年2月10日
Transformer文本分类代码
专知会员服务
116+阅读 · 2020年2月3日
【论文】结构GANs,Structured GANs,
专知会员服务
14+阅读 · 2020年1月16日
知识神经元网络 KNN(简介),12页pdf
专知会员服务
13+阅读 · 2019年12月25日
树形结构为什么不需要归一化?
七月在线实验室
8+阅读 · 2019年4月30日
卷积神经网络四种卷积类型
炼数成金订阅号
18+阅读 · 2019年4月16日
逻辑斯特回归为什么要对特征进行离散化?
七月在线实验室
6+阅读 · 2019年4月1日
从示例中理解SVM算法(附代码)
论智
9+阅读 · 2018年5月10日
BAT题库 | 机器学习面试1000题系列(第191~195题)
七月在线实验室
6+阅读 · 2017年11月15日
干货 | 深度学习之卷积神经网络(CNN)的模型结构
机器学习算法与Python学习
12+阅读 · 2017年11月1日
BAT机器学习面试1000题系列(第46~50题)
七月在线实验室
7+阅读 · 2017年10月7日
机器学习(19)之支持向量回归机
机器学习算法与Python学习
11+阅读 · 2017年10月3日
机器学习(16)之支持向量机原理(二)软间隔最大化
机器学习算法与Python学习
6+阅读 · 2017年9月8日
一文了解各种卷积结构原理及优劣
量子位
9+阅读 · 2017年7月29日
q-Space Novelty Detection with Variational Autoencoders
Neural Arithmetic Logic Units
Arxiv
5+阅读 · 2018年8月1日
Arxiv
6+阅读 · 2018年4月4日
Arxiv
3+阅读 · 2018年1月10日
Arxiv
3+阅读 · 2017年12月18日
VIP会员
相关VIP内容
专知会员服务
42+阅读 · 2020年7月7日
【人大】图实现算法综述与评测分析
专知会员服务
37+阅读 · 2020年4月28日
【图神经网络(GNN)结构化数据分析】
专知会员服务
114+阅读 · 2020年3月22日
自动结构变分推理,Automatic structured variational inference
专知会员服务
38+阅读 · 2020年2月10日
Transformer文本分类代码
专知会员服务
116+阅读 · 2020年2月3日
【论文】结构GANs,Structured GANs,
专知会员服务
14+阅读 · 2020年1月16日
知识神经元网络 KNN(简介),12页pdf
专知会员服务
13+阅读 · 2019年12月25日
相关资讯
树形结构为什么不需要归一化?
七月在线实验室
8+阅读 · 2019年4月30日
卷积神经网络四种卷积类型
炼数成金订阅号
18+阅读 · 2019年4月16日
逻辑斯特回归为什么要对特征进行离散化?
七月在线实验室
6+阅读 · 2019年4月1日
从示例中理解SVM算法(附代码)
论智
9+阅读 · 2018年5月10日
BAT题库 | 机器学习面试1000题系列(第191~195题)
七月在线实验室
6+阅读 · 2017年11月15日
干货 | 深度学习之卷积神经网络(CNN)的模型结构
机器学习算法与Python学习
12+阅读 · 2017年11月1日
BAT机器学习面试1000题系列(第46~50题)
七月在线实验室
7+阅读 · 2017年10月7日
机器学习(19)之支持向量回归机
机器学习算法与Python学习
11+阅读 · 2017年10月3日
机器学习(16)之支持向量机原理(二)软间隔最大化
机器学习算法与Python学习
6+阅读 · 2017年9月8日
一文了解各种卷积结构原理及优劣
量子位
9+阅读 · 2017年7月29日
Top
微信扫码咨询专知VIP会员