無限分級樹形結構是在系統開發中很常見的,如下圖
在之前實現這樣的菜單一直是使用傳統的方法,看數據表結構就一目了然
parent_id記錄其直接父節點,組合樹形結構的關鍵字段;parent_list記錄其所有父節點,便於查詢某個節點下所有子節點(一般使用MySQL的FIND_IN_SET函數),相對冗余。
對於這種結構生成樹形的關鍵算法:根據parent_id組合一個父子(直接關系)節點映射表,即 2 => array(3, 4), 3 => array(5),然后遞歸優先遍歷每個節點的子節點。
如果還需要按順序排列,比如移動某個節點,則需要再添加一個字段來記錄順序。
優點:簡單清晰,通俗易懂,添刪改節點都很容易實現
缺點:查詢效率不高(但一般樹形結構都比較小)
再看另一種實現方式-左右值編碼
其數據結構比較簡單,但左右值需要經過遍歷計算得到,很難看出直接父子關系的節點
通過圖了解左右值如何定義,類似前序遍歷,父節點先訪問,再訪問子節點,遞增標記,每個節點訪問兩次,分左右值。
所有子節點的左右值一定在父節點的左右值范圍,所以通過語句left > $parent_left AND right < $parent_right就可以查詢某個節點下所有的子節點,比FIND_IN_SET優雅。而查找直屬的子節點只需要加上layer = $parent_layer + 1限制。
生成樹操作
只需要按left字段從小到大順序輸出就行,縮進層次的就根據layer值,簡單。
新增操作
比如在服務端類別添加一個子節點Python,一個節點占用2個值,新增的節點就是左右值就是父節點的右值和右值+1,而影響到的節點值(藍色)都是大於等於父節點的右值(即>=10),都是原先的值加2。
新增前
新增后
刪除操作
刪除可參考新增操作,只是將>=父節點右值的所有節點值減去2
移動操作
主要是改變父節點和同層節點調換操作
移動操作基本基於一個公式:任何樹所占的數字數目 = 根的右值 – 根的左值 + 1。
1.改變父節點
先看下移動PHP到客戶端,即PHP的父節點換成客戶端,這種情況下樹節點上的變化
改變前
改變后
以PHP為根的樹(雖然只有一個節點)的節點值變化是以新父節點的原右值為依據,如上圖,新父節點的原右值為6,那PHP新的左值就是6,這樣重新遍歷PHP為根的樹,就相當於這棵子樹上的每個節點更新為:原值 – (根的原左值 – 根的新左值),所以PHP的右值就等於7 = 9 – (8 – 6)。
而除了PHP為根的樹,其他節點更新的有一定范圍,范圍就是新父節點的右值和PHP原先的左值(或舊父節點的左值),因為PHP是向前移,范圍內的節點值是往后移,需要加上PHP樹的所占的數字數目,見上面公式,這里可以總結一個定律:
新父節點_right <= ([left, right] + (移動_right – 移動_left + 1)) < 移動_left
再看下PHP向后移的情況,與前移類似,也是更新一定范圍的節點,變成減定律:
移動_right < ([left, right] – (移動_right – 移動_left + 1)) < 新父節點_left
2.同層節點調換
這里同層節點調換是指兄弟節點順序調換,為了簡化,一般是移到某個兄弟節點(參考點)后面,特殊情況是移到最前,這種情況參考點就是父節點。
如圖,把語言一類移到兄弟節點-數據庫后面。參考點是數據庫,影響范圍是語言的right與數據庫的right之間的值,這類移動屬於后移,根據上面提到的后移減定律,可得到公式:
移動_right < ([left, right] – (移動_right – 移動_left + 1)) <= 參考點_right
而前移公式:參考點_right < ([left, right] + (移動_right – 移動_left + 1)) < 移動_left
如果是移到最前,只是前移公式的范圍的左邊界換為父節點_left,即公式為:父節點_left < ([left, right] + (移動_right – 移動_left + 1)) < 移動_left。
對於是前移還是后移,可以根據移動節點的left值與參考點的right值的大小關系判斷。
至此,關於左右值編碼實現樹形結構的關鍵操作都已說明,主要也是公式,范圍是哪些,是加還是減,我也是糾結了幾天,所以上面顯得有點啰嗦,如果有興趣的還是親手推斷,也許會發現比上面更簡單的更新操作。