1、为什么要使用左右值来表示无限极分类
一般的,我们会使用parent
标志来表示节点之间的上下级关系,好处是结构清晰,修改起来很方便,缺点是查找起来稍微麻烦一点,可能需要使用递归查询,递归的使用将会使性能大幅降低尤其是递归层级多的时候。有些查找,比如统计当前节点的所有子节点个数,这就必须要递归到最后一级,所以在类似于这种需要递归到最后一级才能返回结果的场景,我们可以补充以左右值来提升性能。
以下是常规的数据结构,数据随机生成:
/**
* 生成测试数据
* php think data --action cate
* @return void
*/
protected function cate()
{
$pre = "A";
for ($i = 1; $i <= 20; $i++) {
if ($i == 1) {
$p = 0;
} else {
$p = rand(1, $i-1);
}
$name = $pre . $i;
$this->opDataModel->setTable('test111')->insert([
'id' => $i,
'name' => $name,
'p' => $p,
]);
}
}
对应的树结构
2、左右值如何表示
在上面的树结构上,从根出发,从上到下,从左到右,使用一根连续且不打结的线将所有节点串联起来,并标上序号,效果如下。注意用手指一个一个点来体会。
写入到数据库如下:
然而,我们不可能手动来实现,还是需要借助于程序,先有了树结构之后。
/**
* 将传统模式转换成左右值模式
* php think data --action initCate
* @return void
*/
protected function initCate()
{
$this->n = 1;
$this->initCateDo(0);
}
protected function initCateDo($p)
{
$list = $this->opDataModel->setTable('test111')->where('p', $p)->select();
if($list){
foreach($list as $k => $v){
$this->opDataModel->setTable('test111')->where('id', $v['id'])
->update(['left'=>$this->n++]);
$this->initCateDo($v['id']);
$this->opDataModel->setTable('test111')->where('id', $v['id'])
->update(['right'=>$this->n++]);
}
}
}
如何更新节点
显然,带有左右值的结构在更新节点的时候就没那么容易了,但是往往的无限极分类都是以查询为主的,修改操作占比较少,因此我们还是正常的修改节点,然后调用上面的方法重新生成左右值。
/**
* 增加一个节点
* php think data --action=addNode
* @return void
*/
protected function addNode()
{
$this->opDataModel->setTable('test111')->insert([
'id' => 21,
'name' => 'A21',
'p' => 14,
]);
$this->initCate();
}
4、使用场景
-
查看某个节点下的所有子节点
select * from test111 where id = 10; select * from test111 where `left` > 12 and `left` < 17 ORDER BY `left` ASC;
是不是不用递归就能达到目的了。注意
BETWEEN
包含首尾数值。那么某个节点到底有多少子孙节点呢?很简单,
子孙总数=(右值-左值-1)/2
select COUNT(*) from test111 where `left` > 12 and `left` < 17; // 2
c = (17 - 12 - 1) / 2 = 2
是不是很神奇,当然前提是,你的
left
和right
得是连续的。 -
查找某个节点的上游路径
select * from test111 where `left` < 12 and `right` > 17 order by `left` asc;
-
增删节点
移动节点可以理解为先删再增。
上面已经讲到,可以通过初始化左右值的方式来达到效果,但是也有效率更高的方式,可以分析规律。
比如上面的例子在
A14
下面增加A21
,而A14
为(23,28)
,此时不好确定A21
的左右值,但是我们知道在这个串联的线上,处于A14
后面的所有节点都需要往后挪动一个位置,也就是左右值分别加2。update test111 set `left` = `left` + 2 where `left` >= 28; update test111 set `right` = `right` + 2 where `right` >= 28; update test111 set `right` = 29 where id = 21;
如果
A21
存在子节点,那么所有的子节点的左右值加1,此时需要通过p值来判断。左值的话就要分情况了,如果
A21
没有子节点,那么左值为28,如果有子节点,那么就是子节点的左值减1,放心此时它最多只有一个子节点,或者没有子节点。看来增加节点的操作还是有点麻烦的。
删除节点就简单一些了,也可以参照着推算。
-
显示树结构
两种方式都可以。
-
列出一级分类节点
显然传统的方式更加适合。
5、总结
综上所述,在存储上,还是建议两种方式都存起来,在不同的场景下使用不同的方式。对于层次多数量大的结构使用第二种方法。如果查询量较小但是需要频繁添加和更新的数据,则第一种方法更为简便。