数据结构:评论盖楼设计,支持无限层级

本文介绍了如何使用左右值法优化树形结构数据的查询和操作,如增删节点、查找祖先和后代节点。这种方法通过两次查询即可获取所需数据,提升查询效率。同时,文章给出了在评论、用户裂变等场景下的应用,并提供了数据库表结构和示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一部分:问题抛出

常见的设计,采用标准的树形结构,每个结点记录父ID(pid)。利用pid查询子集时,一次只能查询出一层,查询多层时,逻辑代码将会非常繁琐,而且无法一次查询出子集的数量等等,另外多次查询效率不高。

推荐一种数据结构,大部分情况两次查询,可找出所有需要的数据,减少查询次数,提升性能,本质也是树。可应用于评论用户裂变地域分类等,可以支持无限层级

第二部分:原理介绍
基本结构

在这里插入图片描述
每个节点有三个属性,节点名称、左属性、右属性。其中左属性右属性为关键属性。

public class Node {
   
   
    private String name;
    private int lft;
    private int rght;
}
规律
  1. 祖先节点的左 < 后代节点的左;祖先节点的右 > 后代节点的右
  • 节点(I)左右分别是6、7,其父节点(E)的左右分别是5、8;
  • 节点(C)左右分别是12、17,其子节点(G)左右分别是13、14,子节点(H)是15、16;
  • 节点(B)左右为2、11,其后代节点(D、E、F、I)中最小的左是3,最大的右是10;
  • 节点(A)左右为1、18,其后代节点(B、C、D、E、F、G、H、I)中最小的左是2,最大的右是17;
  1. 同一节点右左之差,除以2并向下取整,即为后代节点数量
  • 节点(I)的后代节点数量 = ( 7 - 6 ) / 2 = 0;
  • 节点(E)的后代节点数量 = ( 8 - 5 ) / 2 = 1;
  • 节点(B)的后代节点数量 = ( 11 - 2 ) / 2 = 4;
  • 节点(A)的后代节点数量 = ( 18 - 1 ) / 2 = 8;
找后代节点
  • 如找节点(E)的后代节点,条件:左 > 5 AND 右 < 8,结果是节点(I);
  • 如找节点(B)的后代节点,条件:左 > 2 AND 右 < 11;结果是结点(D、E、F、I);
  • 如找节点(A)的后代节点,条件:左 > 1 AND 右 < 18;结果是结点(B、C、D、E、F、G、H、I);
    如果查询的结果也想包含自己,则上述条件换成 >= 和 <=
找祖先节点
  • 如找节点(B)的祖先节点,条件:左 < 2 AND 右 > 11,结果是节点(A);
  • 如找节点(E)的祖先节点,条件:左 < 5 AND 右 > 8;结果是(A、B);
  • 如找节点(I)的祖先节点,条件:左 < 6 AND 右 > 7;结果是(A、B、E);
    如果查询的结果也想包含自己,则上述条件换成 <= 和 >=
增加节点(单个节点)

假设在节点(E)下增加子节点(J),注意只能加到最后(可以理解为,他最晚出生,他是最小的弟弟,其他的都是哥哥)。

  1. 为新增节点(J)设置左右属性,左 = 父节点的右;右 = 左 + 1;
  2. 修改祖先节点的右属性,右 = 右 + 2 WHERE 左 < J(左) && 右 >= J(左),这里涉及到节点(A、B、E);
  3. 修改右侧节点的左右属性,SET 左 = 左 + 2, 右 = 右 + 2 WHERE 左 >= J(右),这里涉及节点(F、G、H、C);
    变化如下:
    在这里插入图片描述

插入已完成,可以尝试着把示例图完整画一遍。注意,除第一个节点(A)的左右手动设置外,其余的节点的都是动态算出来的。示例中左是从1开始,可以使用任何一个自然数,如:-1、3678、7758、10000等等;

删除节点(单个节点)

如上例中,删除结点(D)

  • 步骤如下:
  1. 修改祖先节点的右属性,SET 右 = 右 - 2 WHERE 左 < D(左) AND 右 > D(右),这里涉及到结点(A、B);
  2. 修改右侧节点左右属性,SET 左 = 左 - 2, 右 = 右 - 2 WHERE 左 > D(右),这里涉及节点(E、I、J、F、G、H、C);
    变化如下:
    在这里插入图片描述
物理删除节点(带子节点的节点)

利用递归,先删除子节点,再删除自己。(真实的使用场景中,建议使用逻辑删除,不做物理删除
伪代码如下:

public void del(node) {
   
   
	List childs = node.childs() // 包含自己,按左属性降序;
	if(childs != empty) {
   
   
	    for;;
	        del(childs[i]);
	    return;
	}
	// 物理删除结点
}
更换节点(单个节点)

先做删除,再做添加,如上图把节点J挂到H下,
伪代码如下:

	del(J);
	add(J, H);
}

上面是基础的增删操作,下面介绍几种复杂的情况,主要还是利用循环或递归,执行删除、添加操作;

更换节点(带子节点)

如把E节点,挂在C下,推荐的做法是先删除节点,再添加;伪代码如下:

	List childs = E.childs(); // 包含自己,注意排序为:[E, I, J]
	
	for (int i = childs.length - 1; i >= 0; i--) {
   
    // 注意这里是倒序删除,del(J)、del(I)、del(E)
		del(childs.get(i));
  	}
  
  	// 注意这里是正序添加
  	add(E, C);
  	add(I, E);
  	add(J, E);
添加节点(插入指定位置)

如想把新节点K挂在C下,并且在G和H之间。上面得知,新加的节点,只能加到最后,这种情况的处理,伪代码如下:

	add(K, C);
	del(H);
	add(H, C
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值