Post at 2012-06-13 02:03:33php无限分类左右值预排序树
网站,一定离不开多级分类:小到导航栏的两三级分类,大到其它货物的无限分类等。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.
create
table
`navs`(
02.
`id`
int
primary
key
auto_increment,
03.
`
name
`
varchar
(32)
not
null
,
04.
`lft`
int
not
null
,
05.
`rght`
int
not
null
06.
);
07.
insert
into
`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.
class
EndlessByLR
06.
{
07.
function
insert(
$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.
class
EndlessByLR{
03.
//......
04.
//......
05.
//接上面的代码
06.
function
move(
$formid
,
$toid
,
$derect
)
07.
{
08.
if
(
$formid
==
$toid
)
09.
{
10.
return
true;
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