如何存储分销社交电商中用户间的推荐关系之二 [算法之美]

继上文:如何存储分销社交电商中用户间的推荐关系之一 [算法之美]

五、扩展

(1)获取某节点在树中所处的层级,及直接的上级节点

为了更为直观地展现树形结构,我们需要知道某节点在树中所处的层次,以及直接的上级节点,添加了新的记录节点层次的字段 deep,parent_name。用数据上的冗余,换取了一定的查询效率和便捷。

ALTTER TABLE relation ADD COLUMN
`deep` int(11) NOT NULL DEFAULT '0' COMMENT '节点深度';


ALTTER TABLE relation ADD COLUMN
  `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父类ID';

(2)获取某一个节点所有上级节点

有的时候,我们需要知道一个叶子节点所有的上级路径,例如“曹奂”的所有上级:

SELECT parent.* FROM relation AS node, relation AS parent 
WHERE node.`left` BETWEEN parent.`left` 
AND parent.`right` AND node.`name` = '曹奂'
ORDER BY parent.`left` ASC;

(3)多棵树

到目前为止,我们讨论都是集中在三国人物图谱,也就是一棵树。如果想将图谱向历史人物图谱,三国前后的朝代扩展,例如需要增加汉朝,西晋与东晋,等等,那么需要面对一个问题,多棵树如何存储?

方案 A:插入最顶级节点:它的左右值与树中目前最大的右值相关:左值=最大右值+1,右值=最大右值+2。

相当于有一个虚拟的始祖节点 0,所有的顶级节点都是它的子节点。

缺点:

  1. 实现中代码无法直观和高效的区分出多棵树,

  2. 插入一个顶级节点,需要查询树的最大右值,

  3. 前面的树插入节点时,需要修改到后面树的左右值,随着数据的增多,写操作所涉及的数据可能会非常频繁和耗时冗长。这点最为重要,导致最终没有采用此方案。

方案 B:添加一个团队编号标记:team_id,用这个标记来区分不同的树。左值直接重新取1。

实践中最终采用了扩展标记的方案,在对多颗树的操作中,都带上了团队标记(team_id),进行对单棵树的定位。

ALTTER TABLE relation ADD COLUMN
`team_id` int(11) NOT NULL DEFAULT '0' COMMENT '所在团队编号';

六、写性能的改进方案

前面提到,预排序前置遍历树的算法在查询时效率大大的提高了,但是在写方面却有着天然的缺陷。那么在遇到短时间用户涌入,引起大规模写入的时候,应该怎么避免或者减少对数据库的冲击呢?

方案 A:

  1. 在用户表增加了一个字段,记录直接的父节点,

  2. 在用户瞬间大量涌入时,只插入用户基本信息和父节点编号,

  3. 随后将写推荐关系写入的过程拆解到消息队列中实现。

实践中采用了消息队列的方案,具体实现过程不再赘述。

方案 B:

用浮点型值代替目前的整型值记录左右值,称为“非连续型预排序前序遍历树算法”。

  1.  将left、right这两个字段改为浮点型数据(float)。 

  2. 新增加一个节点的方法,同样是在“曹操”节点增加“曹冲”子节点(为第三层节点),这样其它节点都不用更新left、right这两个字段: 

  3. INSERT INTO relation(`name`, `left`, `right`) 
    VALUES('曹冲', 2.1, 2.9);
    
  4. 根节点初始化为一个比较大的实数范围:left=1000000,right=9000000),然后新增的子节点可以分配这个实数范围的其中一个数据段。

即:某节点按宽度(左右值的差值)平均分配为1000个数据段,新增的999个节点取前999个数据段,最后第1000个数据段,再次切分为1000个子数据段,给999节点后续的新增节点。

缺点:

  1. 计算左右值的代码略复杂,不够明了,维护和检查左右值时略复杂,不利于维护,

  2. 某个节点的左右值的增长可能会超过浮点所能容纳的长度。如果实践中采用,需要定期检查数据段是否够用。

七、迁移

有可能实际情况中,早期为了快速开发上线,使用了常见的解决方案,数据结构是经典的<id, parent_id>形式,有没有可能迁移到预排序的数据结构方案来呢?换句话说,有没有算法,可以根据记录父节点的数据记录,重构出这种前序遍历树结构的数据呢?

方案A:

选取所有树的顶级节点,遍历关系表中所有的下级,然后按上面的添加子节点的方法,构建一层节点。然后递归到下一层,直至最终叶子节点-也就是没有下级的节点。

根据这个方案,遍历一个2000多人推荐关系的团队信息后,重建预排序左右值关系数据,大约需要10~20分钟。而且随着团队内的人数、团队的数量的增多,根据记录父节点的数据结构来重构的过程效率呈几何指数的下降,时间变得越来越长,无法用于实际生产环境中。

方案B:

仍然是取出所有树的顶级节点,遍历所有下级,但是不直接写数据库,而是用二叉树的孩子兄弟表示法数据存储结构将其临时存储在内存中,然后用先进先出的队列模式,输出创建预排序左右值关系数据的SQL语句,最后一次性批量插入数据库。

树的二叉链表(孩子-兄弟)存储表示:

这部分为《数据结构》基础课程内容,具体数据结构、代码和输出过程不再赘述。

该方案实践中,同样是2000多人的推荐关系信息,重构过程只用了几十秒。而且因为用的是批量插入语句,单棵树的SQL语句插入时间,不因为数据量的增多而额外变长。

八、总结

我们可以对这种通过左右值编码预排序前序遍历二叉树算法实现无限分组的树形结构设计来存储推荐分销社交电商的用户间推荐关系记录方案做一个总结:

(1)优点:在消除了递归操作的前提下实现了无限分级,而且查询条件是基于整形数字的比较,效率很高。

可以进行先序列表,添加,修改,删除,修改上级等常规操作,满足了业务系统的需求。

(2)缺点:节点的添加、删除、修改上级节点代价较大,需要对整个树进行查询修改,代码复杂度、耦合度极大的增高,修改维护的风险较高。且涉及到表中多条数据的改动,而数据库的写操作是非常费时的IO操作。

参考:

  • 关于前序遍历二叉树预排序算法

  • 预排序遍历树算法牺牲写性能的改进

  • 孩子兄弟表示法(二叉链表树)

  • 树的孩子兄弟表示法及遍历操作

后记:

这篇算法的上半部“之一”发出来后,得到不少朋友和大神的建议和反馈:

@北京-wj-kwai(仓佑嘉措):以a000, ab00, abc0, abcd来分别做ABCD的ID,霍夫曼编码方式不好么。

@小小堂主:我记得有个有向图数据库来的。

@祁宏:我们的方案是数据记录关系变成树结构关系。方法很简单,就是加两个字段。path,level

@江进:你的需求应该用图数据库来解决 而不是传统数据库。

看数据量了,数据量稍微大一些,图数据库比关系型有两个数量级方面的性能差距。

@W1U02:不过这个方案在用户量特别大的时候,更新的量也很惊人啊。

@崔秀龙:做那种老式的树形论坛的时候,也弄了个类似的,每个 root 下面写入东西的时候,都在 root 内做个排序,所以同一个 root 下面,用 主键+顺序号+缩进就能把这个假树撸出来了。

上一篇:如何存储分销社交电商中用户间的推荐关系之一 [算法之美]

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值