預排序遍歷樹算法(非遞歸無限極分類算法)學習筆記


多層數據結構估計所有的web開發者估計都不會陌生,各種軟件的分類都是基於多層結構來設計的。

下面是一個典型的多層數據結構示意圖:


預排序遍歷樹算法(非遞歸無限極分類算法)學習筆記 - Figthing - Figthing的博客

 

相關創建數據語句:
CREATE TABLE category(
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20NOT 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),請看下圖:


預排序遍歷樹算法(非遞歸無限極分類算法)學習筆記 - Figthing - Figthing的博客

上圖依然是表現的與圖一相同的層級關係,但是卻更換了一種表現形式 下面是新的關係表和數據(關係和數據與之前相同,但是表結構不一樣):



CREATE TABLE nested_category (
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20NOT 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中屬於關鍵字 下面我們將插入的數據標識在圖上: 

 預排序遍歷樹算法(非遞歸無限極分類算法)學習筆記 - Figthing - Figthing的博客

同樣,我們將數據標識在原來的結構上:


預排序遍歷樹算法(非遞歸無限極分類算法)學習筆記 - Figthing - Figthing的博客

怎麼樣,是不是很明確了

下面使我自己標定一種形式,方便理解

[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- 1AS 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- 1AS 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, rgtVALUES('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;


總結:

預排序遍歷樹算法的核心就是犧牲了寫的性能來換取讀取的性能

在你的開發的應用遇到此類問題的時(讀壓力 > 寫壓力),嘗試下使用預排序遍歷樹算法來提高你的程序的性能吧。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章