网站,一定离不开多级分类:小到导航栏的两三级分类,大到其它货物的无限分类等。PHP无限分类有多种算法,本文将介绍的是PHP左右值无限分类,也称为预排序树无限分类 ...
什么是左右值无限级分类
左右值无限级分类,也称为预排序树无限级分类,是一种有序的树状结构,位于这些树状结构中的每一个节点都有一个“左值”和“右值”,其规则是:每一个后代节点的左值总是大于父类,右值总是小于父级,右值总是小于左值。处于这些结构中的每一个节点,都可以轻易的算出其祖先或后代节点。因此,可以用它来实现无限分类。
左右值无限分类的优缺点
优点:
- 通过一条SQL就可以获取所有的祖先或后代,这在复杂的分类中非常必要
- 通过简单的四则运算就可以得到后代的数量
缺点
- 分类操作麻烦
- 无法简单的获取子代(请看请“子代”和"后代")
PHP无限分类研究对象
本文用于演示的对象:狼魂博客[http://pjiaxu.com]的导航栏,其结构如下图:
1.
[1]舞文弄墨[8] [9]洞若观火[12] [13]WEB开发[20]
2.
------------------------------------------------------
3.
| | |
4.
| | |
5.
| [10]用户体验[11] |
6.
| |
7.
-------------------------------------------- ----------------------------------
8.
[2]似水流年[3] [4]纸上谈兵[5] [6]隔岸观火[7] [14]PHP[15] [16]JS[17] [18]HTML[19]
其中的[数字]就是每一级的“左右值”,这里的左右值是早已经存在的,我们就以此为例,逐步研究这种算法。
创建PHP左右值无限分类的SQL数据
01.
createtable `navs`(
02.
`id`int primary key auto_increment,
03.
`name`varchar(32) notnull,
04.
`lft`int not null,
05.
`rght`int not null
06.
);
07.
insertinto `navs`(`name`,`lft`,`rght`)
08.
values
09.
('舞文弄墨',1,8),
10.
('似水流年',2,3),
11.
('纸上谈兵',4,5),
12.
('隔岸观火',6,7),
13.
('洞若观火',9,12),
14.
('用户体验',10,11),
15.
('WEB开发',13,20),
16.
('PHP',14,15),
17.
('JS',16,17),
18.
('HTML',18,19);
上图,我为了方便,将SQL有意识的缩进了一下,这应该可以一目了然这种结构了吧。
获取所有的后代节点
了解了PHP预排序村的规则后,我们就可以很清晰的知道如何获取某个节点的所有子代——左值比它大,右值比它小。如,我们要获取“舞文弄墨”的所有子后代,可以使用以下的SQL语句:
1.
select* from `navs` where `lft`>1 and `rght`<8

计算所有子类的数量
有人认为使用 count 配合上面的语句就可以算出,这当然是可以的,但有更快的方式:每有子类节点中每个节点占用两个值,而这些值都是不一样且连续的,那些就可以计算出子代的数量=(右值-左值-1)/2。减少1的原因是排除该节点,你可以想像一个,一个单节点,左值是1,右值是2,没有子类节点,而这时它的右值-左值=1.
插入分类算法
还是先以图为例,比如,现在狼魂的博客[http://pjiaxu.com]发行了一个pblog博客系统,那么它想要在顶级导航上加上pblog这个导航菜单,需要怎么做(这是加顶级菜单的例子)?然而,它又想在Javascript中加入个jQuery分类,又该怎么做?做这些操作有两个重难点:
- 为插入分类分配一个左右值
- 更新其它节点的左右值
结果的示例图如下:
01.
[1]舞文弄墨[8] [9]洞若观火[12] [13]WEB开发[22] [23]pblog[24]
02.
-----------------------------------------------------------------------
03.
| | |
04.
| | |
05.
| [10]用户体验[11] |
06.
| |
07.
-------------------------------------------- ----------------------------------
08.
[2]似水流年[3] [4]纸上谈兵[5] [6]隔岸观火[7] [14]PHP[15] [16]JS[19] [20]HTML[21]
09.
|
10.
[17]jquery[18]
- 插入最顶级节点:它的左右值与该树中最大的右值有关:左值=最大右值+1,右值=最大右值+2,你可以自己模拟一下;
- 插入子节点:它的左右值与它的父级有关:左值=父级的右值,右值=父级的左值+1,这时要更新的数据有:父级的右值,所有左值大于父级左级,右值大于低级右值的节点左右值都应该+2;你可以模拟在jquery下再加一个节点看是不是如此。
有了这些分析,就不难用PHP来实现了,下面,就使用PHP写一个插入算法;
PHP插入节点代码
01.
define('DB_HOST','localhost');
02.
define('DB_USER','root');
03.
define('DB_PWD','`12345');
04.
define('DB_NAME','test');
05.
classEndlessByLR
06.
{
07.
functioninsert($name,$parentid)
08.
{
09.
$db= new mysqli(DB_HOST,DB_USER,DB_PWD,DB_NAME);
10.
if($parentid== 0)
11.
{
12.
//增加最高级,即与WEB开发同级,详见[http://pjiaxu.com/]导航中的Pblog
13.
$sql= 'select max(`rght`) as `maxid` from `navs`;';
14.
$result= $db->query($sql);
15.
//这里基本不用做出错判断,一般都有数据的
16.
$row= $result->fetch_array(MYSQLI_ASSOC);
17.
$lft= $row['maxid']+1;
18.
$rght= $lft+1;
19.
}
20.
else
21.
{
22.
$sql= "select * from `navs` where `id`={$parentid};";
23.
$result= $db->query($sql);
24.
//这里应做个出错判断
25.
$row= $result->fetch_array(MYSQLI_ASSOC);
26.
$rght= $row['rght'];
27.
$sql= "update `navs` set `rght`=`rght`+2 where `rght`>={$rght};";
28.
$db->query($sql);
29.
$sql= "update `navs` set `lft`=`lft`+2 where `lft`>{$rght};";
30.
$db->query($sql);
31.
$lft= $row['rght'];
32.
$rght= $lft+1;
33.
}
34.
$sql= "insert into `navs`(`name`,`lft`,`rght`)values('{$name}',{$lft},{$rght});";
35.
return$db->query($sql);
36.
}
37.
}
1.
$eblr= new EndlessByLR();
2.
$eblr->insert('pblog',0);//0是顶级, 9是JS,在插入之前,你应该是知道的!
3.
$eblr->insert('jquery',9);

你可以花点时间验证一下是不是跟演示图一样。
网上搜索引擎有一个相似的文章占了前几页,在更新又值时是使用 `rght`>{$rght} 而不是 `rght`>={$rght};就我今天目测了N久的得出结果:这是不科学的!
解决了插入的问题,实际上就解决了很多实际应用了。现在,我们需要研究另一种操作:移动节点——这个是要了亲命的操作!
移动节点演示图
我们还是来演示推算一下再到代码中去吧。突然间,狼魂博客[http://pjiaxu.com/map.html]觉得pblog大多的内容都是PHP,那何不把它移动到PHP分类下呢?
01.
[1]舞文弄墨[8] [9]洞若观火[12] [13]WEB开发[22](24) [25]other[26](假设存在)
02.
----------------------------------------------------------------------
03.
| | |
04.
| | |
05.
| [10]用户体验[11] |
06.
| |
07.
-------------------------------------------- --------------------------------------
08.
[2]似水流年[3] [4]纸上谈兵[5] [6]隔岸观火[7] [14]PHP[15](17) (18)[16]JS[19](21) (22)[20]HTML[21](23)
09.
| |
10.
(15)[23]pblog[24](16) (19)[17]jquery[18](20)
01.
[1]舞文弄墨[8] [9]洞若观火[12] [13]WEB开发[24]
02.
----------------------------------------------------------------------
03.
| | |
04.
| | |
05.
| [10]用户体验[11] |
06.
| |
07.
-------------------------------------------- ---------------------------------------
08.
[2]似水流年[3] [4]纸上谈兵[5] [6]隔岸观火[7] [14]PHP[17] [18]JS[21] [22]HTML[23]
09.
| |
10.
[15]pblog[16] [19]jquery[20]
我们继续分析——移动共可以四个方向移动:
- pblog移动到php之下成为它的子类
- pblog移动到php之上成为它的父类
- pblog移动到php左右成为它的兄弟
我们以子类为例子演示推算一下,假设(x1,y1)、(x2,y2)是php和pblog的左右值,那么,我们能过分析得到以下的结论:
- 左值在min(y1,y2)最小右值和max(x1,x2)最大左值之间都需要加2
- 右值在min(x1,x2)最小左值和max(y1,y2)最大右值之间都需要加2
- 移动节点的左值=父类节点的右值,右值=父类节点右值+1
通过以上的分析,就可以写出移动节点的PHP代码。
PHP移动分类函数
01.
<?php
02.
classEndlessByLR{
03.
//......
04.
//......
05.
//接上面的代码
06.
functionmove($formid,$toid, $derect)
07.
{
08.
if($formid== $toid)
09.
{
10.
returntrue;
11.
}
12.
$sql= "select * from `navs` where `id`=$formid
13.
union
14.
select * from `navs` where `id`=$toid";
15.
$db= new mysqli(DB_HOST,DB_USER,DB_PWD,DB_NAME);
16.
$result= $db->query($sql);
17.
$rows= $result->fetch_all(MYSQLI_ASSOC);//php版本要大于5.3才可以用
18.
if($rows[0]['lft'] >$rows[1]['lft'])
19.
{
20.
$maxlft= $rows[0]['lft'];
21.
$minlft= $rows[1]['lft'];
22.
}
23.
else
24.
{
25.
$maxlft= $rows[1]['lft'];
26.
$minlft= $rows[0]['lft'];
27.
}
28.
if($rows[0]['rght'] >$rows[1]['rght'])
29.
{
30.
$minrght= $rows[1]['rght'];
31.
$maxrght= $rows[0]['rght'];
32.
}
33.
else
34.
{
35.
$minrght= $rows[0]['rght'];
36.
$maxrght= $rows[1]['rght'];
37.
}
38.
39.
switch($derect)
40.
{
41.
case'child':
42.
$sql= "update `navs` set `lft`=`lft`+2 where `lft` between $minrght and $maxlft";
43.
$db->query($sql);
44.
//echo $sql;
45.
$sql= "update `navs` set `rght`=`rght`+2 where `rght` between $minlft and $maxrght";
46.
$db->query($sql);
47.
//echo $sql;
48.
$lft= $minlft+1;
49.
$rght= $lft+1;
50.
$sql= "update `navs` set `lft`=$lft,`rght`=$rght where id=$formid limit 1;";
51.
//echo $sql;
52.
$db->query($sql);
53.
break;
54.
case'parent':
55.
break;
56.
case'before':
57.
break;
58.
case'after':
59.
break;
60.
}
61.
}
1.
$eblr->move(11,8,'child');

如要移动看其它位置,看各位模仿移动子类自行分析。
获取所有的父级
这个为什么要放最后,那是——只有在最后,jquery才有直属两个祖先——js和web开发,其实看一下就知道了,获取一个分类的直属祖先(类似于我们平时看到网站的"你所在的位置"一样获取一条直达首页的路径)。规则就是:左值小于该分类,右值大于该分类的节点;如,获取jquery的路径就是:
1.
select* from `navs` where `lft`<17 and `rght`>28
本文详细介绍了PHP无限分类中的预排序树无限分类方法,包括其原理、优缺点及具体实现步骤。从创建SQL数据表开始,逐步解析如何获取后代节点、计算子类数量、插入和移动节点等关键操作。
1237

被折叠的 条评论
为什么被折叠?



