バックグラウンド
プロジェクトでは、組織構造、マルチレベルメニュー、商品タイプなど、マルチレベル分類のサブカテゴリクエリの問題が頻繁に発生します。一般的なデータ構造は次のとおりです。
この設計の問題:
-
マルチレベル分類のサブカテゴリクエリは、再帰を介して実装する必要があり、時間計算量は0(n)であり、効率は比較的低くなります。
-
再帰が深すぎると、スタックオーバーフローが発生しやすくなります。
それで、マルチレベル分類サブカテゴリクエリの場合、より良い解決策はありますか?ツリートラバーサルアルゴリズム(MPTT)を事前に並べ替えることにより、マルチレベル分類のサブカテゴリクエリの問題を解決できます。
序章
MPTT(Modified Preorder Tree Taversal)事前ソートトラバーサルツリーアルゴリズム。主に、階層関係の保存とトラバーサルで使用されます。
基本的
事前ソートトラバーサルツリーアルゴリズムの原則:ツリーレベルの関係は、数値の左右のノードの値で表されます。
以下に示すように:
ツリー構造は、次のようにデータベースの構造データに変換されます。
説明:MPTTデータ構造には、左、右ノード、レベル、親ノードなどの情報が含まれています。
- treeid:ツリーのIDは、データベース内のツリーを識別するために使用されます。
- level:ツリーのレベルを示し、ルートノードのレベルは1であり、子ノードのレベルは親ノードのレベルに1を加えたものです。
- parentId:親ノードID。ルートノードには親ノードがないため、値は-1です。
- lftNode:左ノード値。
- rgtNode:正しいノード値。
基本的なアプリケーション
ツリー全体をトラバースする
ツリー全体をトラバースするには、treeId条件を照会するだけで済みます。
ノードの下のすべての子孫ノードを検索します
Fruitの下にあるすべての子孫ノードを検索するには、左側のノードの値が2より大きく、右側のノードの値が11未満であることを確認するだけで済みます。
SELECT * FROM t_tree t WHERE t.`lftNode`>2 AND t.`rgtNode`<11;
复制代码
検索結果
効率が0(1)のクエリは、再帰クエリよりもはるかに効率的です。
ノードの下のすべての子ノードを検索します
Fruitの下のすべてのサブポイントを見つけるには、2に等しいレベルをクエリするだけです。
SELECT * FROM t_tree t WHERE t.`level`='2';
复制代码
検索結果
ノードのパスを見つける
バナナノードのパスを見つけるには、左側のノードが8未満で、右側のノードが9より大きいことを照会するだけで済みます。
SELECT * FROM t_tree t WHERE t.`lftNode`<8 AND t.`rgtNode`>9;
复制代码
検索結果
クエリ結果から、バナナのノードパスは次のようになります。Food-> Fruit-> Yellow
ノードにはいくつの子孫がありますか?
Fruitの下にあるすべての子孫ノードの数を見つけます。具体的な式は次のとおりです。
子孙总数=(右值-左值-1)/2
复制代码
式の計算により、Fruitの子孫ノードの総数は(18-1-1)/ 2=8であることがわかります。
子ノードを追加する
プリコンパイルされた並べ替えの効率はクエリノードでは非常に高くなりますが、ノードの追加と削除にはノードの再計算と移動が必要であり、すべてが遅くなるため、ノードの追加と削除では非常に非効率的です。
ロジックを実装する
-
存在しないツリーに新しいノードを追加し、新しいツリーを作成します。その場合、親IDは-1、レベルは1であり、treeIdは既存のツリーの最大treeIdに1を加えたものに基づいています。
-
既存のツリーに新しいノードを追加します。parentIdは親ノードのIDであり、levelは親ノードのレベルに1を加えたものであり、treeIdは親ノードと同じです。
-
バランスが崩れている他のノードの左側の値を修復し、すべてのノードの左側の値に、parentIdの右側の値よりも大きい2を追加します。
-
バランスが崩れている他のノードの右辺値を修復し、parentIdの右辺値より大きいすべてのノードの右辺値に2を追加します。
実装
DELIMITER $$
DROP PROCEDURE IF EXISTS `SP_TREE_ADD`$$
CREATE PROCEDURE `SP_TREE_ADD`(
IN p_oid INT (11),
IN p_name VARCHAR (30),
IN p_parentOid INT (11)
)
BEGIN
#获取父节点的相关数据信息
SELECT @myRight :=rgtNode,@oid :=oid,@level :=LEVEL,@treeid :=treeid FROM t_tree t WHERE oid = p_parentOid;
#大于parentId右值的所有节点的右值加2。
UPDATE t_tree SET rgtNode = rgtNode + 2 WHERE rgtNode > @myRight;
#大于parentId右值的所有节点的左值加2
UPDATE t_tree SET lftNode = lftNode + 2 WHERE lftNode > @myRight;
UPDATE t_tree SET rgtNode=rgtNode+2 WHERE oid=@oid;
#插入数据
INSERT INTO t_tree(oid,NAME,parentOid,LEVEL,lftNode,rgtNode,treeid)
VALUES(p_oid, p_name, p_parentOid,@level+1,@myRight, @myRight +1,@treeid);
END$$
DELIMITER ;
复制代码
テスト
Redノードの下にAppleサブノードを追加し、上記のストアドプロシージャを実行します
CALL SP_TREE_ADD('11','apple','3');
复制代码
挿入に成功すると、結果は次のようになります。
赤の下のノードの数を再確認するには、左側のノードが3より大きく、右側のノードが8より小さい場合にのみクエリを実行する必要があります。
SELECT * FROM t_tree t WHERE t.`lftNode`>3 AND t.`rgtNode`<8;
复制代码
子ノードを削除する
ロジックを実装する
- 左の値が親ノードより大きく、右の値より小さいノードを削除します
- バランスが崩れている他のノードの左側の値を修復し、parentIdの右側の値よりも大きいすべてのノードの左側の値から差の値を減算します。
- バランスが崩れている他のノードの右辺値を修復し、parentIdの右辺値よりも大きいすべてのノードの右辺値から差を差し引きます。
実装
DELIMITER $$
DROP PROCEDURE IF EXISTS `SP_TREE_DEETE`$$
CREATE PROCEDURE `SP_TREE_DEETE`(
IN p_Oid INT (11)
)
BEGIN
SELECT
@myleft := lftNode,
@myright :=rgtNode,
@oid :=oid,
@mywidth := rgtNode-lftNode+1
FROM t_tree WHERE oid = p_Oid;
DELETE FROM t_tree WHERE lftNode BETWEEN @myleft AND @myright;
UPDATE t_tree SET rgtNode = rgtNode - @mywidth WHERE rgtNode > @myright;
UPDATE t_tree SET lftNode = lftNode - @mywidth WHERE lftNode > @myright;
END$$
DELIMITER ;
复制代码
テスト
Redノードの下のappleサブノードを削除し、上記のストアドプロシージャを実行します。
CALL SP_TREE_DEETE(11);
复制代码
削除後の結果は以下のとおりです。
赤の下のノードの数を再確認するには、左側のノードが3より大きく、右側のノードが6より小さい場合にのみクエリを実行する必要があります。
SELECT * FROM t_tree t WHERE t.`lftNode`>3 AND t.`rgtNode`<6;
复制代码
長所と短所
アドバンテージ
ソート前のトラバーサルツリーのクエリ効率は高く、クエリの効率は分類レベルの増加による影響を受けず、多くのクエリ操作を伴うシナリオに適しています。
欠点
追加と削除にはノードの左右の値を再計算する必要があるため、実行効率は低くなります
要約する
この記事では、事前ソートアルゴリズムについて説明します。完璧なソリューションはありません。その長所と短所をマスターする必要があります。実際のプロジェクトでは、特定のビジネスシナリオに応じて適切なソリューションを選択する必要があります。
ナゲッツテクノロジーコミュニティのクリエイター署名プログラムの募集に参加しています。リンクをクリックして登録し、送信してください。