预排序遍历树算法
嵌套集模型(The Nested Set Model)示意图
上图表现了数据新的层级关系,关系表和数据如下
CREATE TABLE nested_category ( category_id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(20) NOT NULL, lft INT NOT NULL, rgt INT NOT NULL ); INSERT INTO nested_category VALUES(1,'ELECTRONICS',1,20),(2,'TELEVISIONS',2,9),(3,'TUBE',3,4), (4,'LCD',5,6),(5,'PLASMA',7,8),(6,'PORTABLE ELECTRONICS',10,19), (7,'MP3 PLAYERS',11,14),(8,'FLASH',12,13), (9,'CD PLAYERS',15,16),(10,'2 WAY RADIOS',17,18); SELECT * FROM nested_category ORDER BY category_id;
这里将 left,right 修改为 lft,rgt因为这两个词在MYSQL中属于关键字 下面我们将插入的数据标识在图上:
同样,我们将数据标识在原来的结构上:
怎么样,是不是很明确了
下面使我自己标定一种形式,方便理解
[1
[2
[3 4]
[5 6]
[7 8]
9]
[10
[11
[12 13]
14]
[15 16]
[17 18]
19]
20]
--遍历整个树,查询子集 条件:左边 > 父级L, 右边 < 父级R
1 SELECT node.name 2 FROM nested_category AS node, 3 nested_category AS parent 4 WHERE node.lft BETWEEN parent.lft AND parent.rgt 5 AND parent.name = 'ELECTRONICS' 6 ORDER BY node.lft; 7 8 +----------------------+ 9 | name | 10 +----------------------+ 11 | ELECTRONICS | 12 | TELEVISIONS | 13 | TUBE | 14 | LCD | 15 | PLASMA | 16 | PORTABLE ELECTRONICS | 17 | MP3 PLAYERS | 18 | FLASH | 19 | CD PLAYERS | 20 | 2 WAY RADIOS | 21 +----------------------+
--查询所有无分支的节点 条件:右边 = 左边L + 1
1 SELECT name 2 FROM nested_category 3 WHERE rgt = lft + 1;
--查询某个字节点到根节点的路径
1 SELECT parent.name 2 FROM nested_category AS node, 3 nested_category AS parent 4 WHERE node.lft BETWEEN parent.lft AND parent.rgt 5 AND node.name = 'FLASH' 6 ORDER BY parent.lft; 7 8 9 SELECT node.name, (COUNT(parent.name) - 1) AS depth 10 FROM nested_category AS node, 11 nested_category AS parent 12 WHERE node.lft BETWEEN parent.lft AND parent.rgt 13 GROUP BY node.name 14 ORDER BY node.lft;
--查询子节点的深度
1 SELECT node.name, (COUNT(parent.name) - (sub_tree.depth + 1)) AS depth 2 FROM nested_category AS node, 3 nested_category AS parent, 4 nested_category AS sub_parent, 5 ( 6 SELECT node.name, (COUNT(parent.name) - 1) AS depth 7 FROM nested_category AS node, 8 nested_category AS parent 9 WHERE node.lft BETWEEN parent.lft AND parent.rgt 10 AND node.name = 'PORTABLE ELECTRONICS' 11 GROUP BY node.name 12 ORDER BY node.lft 13 )AS sub_tree 14 WHERE node.lft BETWEEN parent.lft AND parent.rgt 15 AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt 16 AND sub_parent.name = sub_tree.name 17 GROUP BY node.name 18 ORDER BY node.lft;
--插入新节点
1 LOCK TABLE nested_category WRITE; 2 SELECT @myRight := rgt FROM nested_category 3 WHERE name = 'TELEVISIONS'; 4 UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myRight; 5 UPDATE nested_category SET lft = lft + 2 WHERE lft > @myRight; 6 INSERT INTO nested_category(name, lft, rgt) VALUES('GAME CONSOLES', @myRight + 1,@myRight + 2); 7 UNLOCK TABLES;
先找到右边的值,例如节点TELEVISIONS右边的值
1 LOCK TABLE nested_category WRITE; 2 SELECT @myRight := rgt FROM nested_category 3 WHERE name = 'TELEVISIONS';
在 R = 9(L8, R9)与 L = 10(L10,R11) 节点之间插入一个新节点
那么所有 左值 和 右值 > 9 的节点的左值和右值需要 + 2
1 UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myRight; 2 UPDATE nested_category SET lft = lft + 2 WHERE lft > @myRight;
左值 = 插入位置左边节点记录的右值 + 1, 右值 = 插入位置左边节点记录的右值 + 2
1 INSERT INTO nested_category(name, lft, rgt) VALUES('GAME CONSOLES', @myRight + 1,@myRight + 2); 2 UNLOCK TABLES;
新节点右边的节点(L10,R11)左值右值都需要 + 2 那么插入后的新值为 L12 R13
新节点的左值为 9 + 1 = 10 右值为 9 + 2 = 11
--删除新节点
删除节点的算法与添加一个节点的算法相反
删除一个没有子节点的节点
1 LOCK TABLE nested_category WRITE; 2 SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1 3 FROM nested_category 4 WHERE name = 'GAME CONSOLES'; 5 DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight; 6 UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight; 7 UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight; 8 UNLOCK TABLES;
删除一个分支节点和它所有的子节点
1 LOCK TABLE nested_category WRITE; 2 SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1 3 FROM nested_category 4 WHERE name = 'MP3 PLAYERS'; 5 DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight; 6 UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight; 7 UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight; 8 UNLOCK TABLES;
删除一个节点后移动到其他节点
1 LOCK TABLE nested_category WRITE; 2 SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1 3 FROM nested_category 4 WHERE name = 'PORTABLE ELECTRONICS'; 5 DELETE FROM nested_category WHERE lft = @myLeft; 6 UPDATE nested_category SET rgt = rgt - 1, lft = lft - 1 WHERE lft BETWEEN @myLeft AND@myRight; 7 UPDATE nested_category SET rgt = rgt - 2 WHERE rgt > @myRight; 8 UPDATE nested_category SET lft = lft - 2 WHERE lft > @myRight; 9 UNLOCK TABLES;
总结:
预排序遍历树算法的核心就是牺牲了写的性能来换取读取的性能
在你的开发的应用遇到此类问题的时(读压力 > 写压力),尝试下使用预排序遍历树算法来提高你的程序的性能吧。
参考地址:http://be-evil.org/post-168.html
http://www.cnblogs.com/BigIdiot/archive/2013/04/02/2995248.html