什麼是預排序遍歷樹算法(MPTT)

在瞭解什麼是『預排序遍歷樹算法』之前,我們先思考一個問題如何處理『多級分類的子分類查詢』。例如:
多級分類
要存儲表示層級關係的數據,一種最簡單的方案,存儲當前分類的名稱,以及上一級分類的名稱,通常我們稱這種存儲結構爲『鄰接表』。

數據庫存儲結構:

分類名稱 父級分類
food
fruit food
meat food
apple fruit
breef meat
pork meat

這種方式有什麼問題:查詢效率過低。當我們在程序裏查詢 food 的子分類時,要先在數據庫中,查詢 food 的一級子分類(fruit、meat)、在對一級分類的子分類進行遞歸查詢,需要進行 logN 次( N 爲子分類個數) I/O 操作(向數據庫進行查詢),這樣的查詢效率不高,但是很容易實現。

而 MPTT 預排序生成樹算法正是爲了解決多層級關係數據的查詢效率問題。

MPTT 是什麼?

MPTT(Modified Preorder Tree Taversal)預排序遍歷樹算法,主要應用於層級關係的存儲和遍歷。

在 MPTT 中是如何表示層級關係的:
MTTP 表示層級關係
MTTP 不直接存儲父分類信息,而是通過 left、right 指來表示層級關係。

還是以 food 爲例,我們從頭開始構建一棵『預排序遍歷樹』。

# 創建數據表,left_value、right_value 表示左右子樹區間
CREATE TABLE `product_category` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  category_name varchar(20) not null,
  left_value int(10) not null,
  right_value int(10) not null,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然後開始插入 food 的一級子分類,根據當前分類的 left_value 來確定插入分類的左右值,同時更新受影響的其他分類。
插入子分類
show code,插入子分類

INSERT into product_category(category_name,`left_value`,`right_value`)
values('food',1,2); # 插入 food 數據
# 鎖表,防止被同時修改,數據不一致
lock table `product_category` write;
# 插入 food 子分類
select @myLeft := `left_value` 
from `product_category`
where category_name='food';
# 更新其他受影響分類左右區間值
update product_category SET left_value=left_value+2 
where left_value>@myLeft;
update product_category set right_value=right_value+2 
where right_value>@myLeft;
# 插入子分類信息
INSERT into product_category(category_name,`left_value`,`right_value`)
values('meat',@myLeft+1,@myLeft+2);
unlock tables; # 解除表鎖定

除了插入子分類的方式,我們還可以選擇 插入同級分類,和插入子分類的區別在於,插入點的左右值取決於被插入點的 right_value。

# 插入 meat 同級分類
lock table `product_category` write;
select @myRight := `right_value` 
from `product_category`
where category_name='meat';
update product_category SET left_value=left_value+2 
where left_value>@myRight;
update product_category set right_value=right_value+2 
where right_value>@myRight;
INSERT into product_category(category_name,`left_value`,`right_value`)
values('fruit',@myRight+1,@myRight+2);
unlock tables;

刪除指定分類,也等於刪除包括指定分類在內的所有子分類,所有受影響左右子分類都受影響。

# 刪除 meat 分類
lock table `product_category` write;
select @myLeft:= `left_value`,@myRight:= `right_value`,@myDiff:=@myRight-@myLeft+1
from `product_category`
where `category_name`='meat';
delete from `product_category`
where `left_value`>=@myLeft and `right_value`<=@myRight;
update `product_category`
set `right_value`=`right_value`-@myDiff
where `right_value`>@myRight;
update `product_category`
set `left_value`=`right_value`-@myDiff
where `left_value`>@myLeft;
unlock tables;

那如何 查詢指定分類的子分類 呢?

select @myLeft:= `left_value`,@myRight:= `right_value`
from `product_category`
where `category_name`='meat'; # 指定分類名稱
select * 
from `product_category`
where `left_value`>=@myLeft and `right_value`<=@myRight; # 不包括指定分類,去掉等號即可

通過 MPTT 我們查詢某一特定分類的子分類信息只要做兩次查詢操作,解決了『鄰接表』遞歸查詢的低效查詢問題。

鄰接表』和『預排序遍歷樹』的優劣

鄰接表』,適用於 增刪改 操作較多的場景,每次刪改只需要修改一條數據。在查詢方面,隨着 分類層級的增加鄰接表』的遞歸查詢效率逐漸降低。

預排序遍歷樹』,適用於 查詢 操作較多的場景,查詢的效率不受分類層級的增加的影響,但是隨着數據的增多,每增刪數據,都要同時操作多條受影響數據,執行效率逐漸下降。

具體要選擇哪一種存儲結構和算法,需要根據具體的應用場景來做選擇。

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