多層數據結構估計全部的web開發者估計都不會陌生,各類軟件的分類都是基於多層結構來設計的。
下面是一個典型的多層數據結構示意圖:node
相關建立數據語句:
CREATE TABLE category(
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
parent INT DEFAULT NULL);
INSERT INTO category
VALUES(1,'ELECTRONICS',NULL),(2,'TELEVISIONS',1),(3,'TUBE',2),
(4,'LCD',2),(5,'PLASMA',2),(6,'PORTABLE ELECTRONICS',1),
(7,'MP3 PLAYERS',6),(8,'FLASH',7),
(9,'CD PLAYERS',6),(10,'2 WAY RADIOS',6);
SELECT * FROM category ORDER BY category_id;
在這種數據結構中,各層之間經過字段 parent 來造成鄰接表,咱們查詢某些層級的關係的時候通常都是經過遞歸的方式,遍歷某個層級關係的SQL的查詢次數會順着層級的增長,想一想在層級有20的時候,根據某個底層節點取它到頂層節點的查詢次數吧。
爲了解決這個問題,人們想出了嵌套集模型(The Nested Set Model),請看下圖:web
上圖依然是表現的與圖一相同的層級關係,可是卻更換了一種表現形式 下面是新的關係表和數據(關係和數據與以前相同,可是表結構不同):算法
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中屬於關鍵字 下面咱們將插入的數據標識在圖上: 數據結構
一樣,咱們將數據標識在原來的結構上:ide
怎麼樣,是否是很明確了
下面使我本身標定一種形式,方便理解
[1
[2
[3 4]
[5 6]
[7 8]
9]
[10
[11
[12 13]
14]
[15 16]
[17 18]
19]
20]
遍歷整個樹,查詢子集 條件:左邊 > 父級L, 右邊 < 父級R
SELECT node.name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND parent.name = 'ELECTRONICS'
ORDER BY node.lft;
+----------------------+
| name |
+----------------------+
| ELECTRONICS |
| TELEVISIONS |
| TUBE |
| LCD |
| PLASMA |
| PORTABLE ELECTRONICS |
| MP3 PLAYERS |
| FLASH |
| CD PLAYERS |
| 2 WAY RADIOS |
+----------------------+
- 查詢全部無分支的節點 條件:右邊 = 左邊L + 1
SELECT name
FROM nested_category
WHERE rgt = lft + 1;
- 查詢某個字節點到根節點的路徑
SELECT parent.name
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.name = 'FLASH'
ORDER BY parent.lft;
SELECT node.name, (COUNT(parent.name) - 1) AS depth
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.name
ORDER BY node.lft;
- 查詢子節點的深度
SELECT node.name, (COUNT(parent.name) - (sub_tree.depth + 1)) AS depth
FROM nested_category AS node,
nested_category AS parent,
nested_category AS sub_parent,
(
SELECT node.name, (COUNT(parent.name) - 1) AS depth
FROM nested_category AS node,
nested_category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.name = 'PORTABLE ELECTRONICS'
GROUP BY node.name
ORDER BY node.lft
)AS sub_tree
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt
AND sub_parent.name = sub_tree.name
GROUP BY node.name
ORDER BY node.lft;性能
- 插入新節點
算法詳解:
1.全部分類 左邊和右邊的值 > 插入節點的左邊節點記錄的右值 的所有 + 2
2.插入節點 左值 = 插入位置左邊節點記錄的右值 + 1, 右值 = 插入位置左邊節點記錄的右值 + 2
例子:
在 R = 9(L8, R9)與 L = 10(L10,R11) 節點之間插入一個新節點
那麼全部 左值 和 右值 > 9 的節點的左值和右值須要 + 2
例如新節點右邊的節點(L10,R11)左值右值都須要 + 2 那麼插入後的新值爲 L12 R13
新節點的左值爲 9 + 1 = 10 右值爲 9 + 2 = 11
SQL語句實現
LOCK TABLE nested_category WRITE;
SELECT @myRight := rgt FROM nested_category
WHERE name = 'TELEVISIONS';
UPDATE nested_category SET rgt = rgt + 2 WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft + 2 WHERE lft > @myRight;
INSERT INTO nested_category(name, lft, rgt) VALUES('GAME CONSOLES', @myRight + 1, @myRight +2);
UNLOCK TABLES;
- 刪除新節點
刪除節點的算法與添加一個節點的算法相反
刪除一個沒有子節點的節點
LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
FROM nested_category
WHERE name = 'GAME CONSOLES';
DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;
UNLOCK TABLES;
刪除一個分支節點和它全部的子節點
LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
FROM nested_category
WHERE name = 'MP3 PLAYERS';
DELETE FROM nested_category WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;
UNLOCK TABLES;學習
刪除一個節點後移動其字節點到
LOCK TABLE nested_category WRITE;
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
FROM nested_category
WHERE name = 'PORTABLE ELECTRONICS';
DELETE FROM nested_category WHERE lft = @myLeft;
UPDATE nested_category SET rgt = rgt - 1, lft = lft - 1 WHERE lft BETWEEN @myLeft AND @myRight;
UPDATE nested_category SET rgt = rgt - 2 WHERE rgt > @myRight;
UPDATE nested_category SET lft = lft - 2 WHERE lft > @myRight;
UNLOCK TABLES;spa
總結:
設計
預排序遍歷樹算法的核心就是犧牲了寫的性能來換取讀取的性能orm
在你的開發的應用遇到此類問題的時(讀壓力 > 寫壓力),嘗試下使用預排序遍歷樹算法來提升你的程序的性能吧。