無限極分類樹的原理
無限極分類的原理非常簡單,就是把每一個分類節點規定一個左值和右值來確定該節點在整個分類中的位置。一般的我們還會加上一個深度(層級)的值,表示他所處的深度。
這樣的一個好處是對於層級很深或者節點非常多的,比較龐大的分類或其他樹我們想查找或者遍歷就顯得非常快。例如我們要查找所有的電腦辦公分類下的所有後代分類。如果你的數據只是id和parent_id約束的結構那麼你先要以電腦辦公的id作爲parent_id查找出下一級分類然後再以下一級分類的id作爲parent_id查找再下一級分類一直到找不到子分類爲止。這對於深度比較大的分支來說查詢就非常非常麻煩。
但是你使用樹狀結構來查詢電腦辦公分類下的所有的後代分類只要查找左值大於14右值小於23的節點即可,一次就可以查出來非常方便。
樹結構在關係型數據庫中的存儲圖解
對於下邊這個結構的分類樹來說我們給每一個節點標出左值和右值。(其實在開發中我們不去整體的關心這些問題)
在數據表中的結構
id | parent_id | name | level | left_key | right_key | description | status |
---|---|---|---|---|---|---|---|
1 | 0 | 數碼電器 | 1 | 1 | 24 | 數碼電器大分類 | 1 |
20 | 1 | 家用電器 | 2 | 2 | 7 | 1 | |
21 | 20 | 大家電 | 3 | 3 | 4 | 1 | |
22 | 20 | 小家電 | 3 | 5 | 6 | 1 | |
23 | 1 | 電腦辦公 | 2 | 14 | 23 | 電腦及辦公電器 | 1 |
24 | 1 | 手機通訊 | 2 | 8 | 13 | 手機通訊類 | 1 |
25 | 24 | 遊戲手機 | 3 | 9 | 10 | 1 | |
26 | 24 | 拍照手機 | 3 | 11 | 12 | 1 | |
27 | 23 | 臺式機 | 3 | 15 | 16 | 1 | |
28 | 23 | 筆記本 | 3 | 17 | 22 | 1 | |
29 | 28 | 輕薄本 | 4 | 18 | 19 | 超極本等 | 1 |
30 | 28 | 遊戲本 | 4 | 20 | 21 | 高性能遊戲本 | 1 |
因爲在維護數據的結構關係時會變的僅僅是level,parent_id,left_key,right_key而id和其他數據不會改變所以在使用中不會出現歧義。
性能分析
主要的性能消耗是在結構的維護中,也就是說一般情況下我們會在後臺來添加這些分類等數據。在我們再樹的頂端插入節點或移動節點的時候會重新計算level,left_key,right_key,parent_id的時候會更新整個表或者表的一部分。但是這個操作一般在後臺完成,不會對用戶體驗的性能造成太大的影響,在查詢中我們同樣可以創建一些緩存,是非常簡單的。
php語言實現
在過去我發佈過一個PHP下的實現包,但是是基於thinkphp5來實現的,爲了消除其對於PHP框架的依賴性,我重構了這個包,並且在github上https://github.com/gmars/infinite-tree中重提交了該包,新的名稱叫做gmars/infinite-tree 使用方法如下:
infinite-tree
php的無限樹工具包
之前我已經寫過一個無限級分類的PHP包名稱叫tp5-nestedsets在packagist上的安裝量還是挺大的。但是tp5-nestedsets是基於tp5的很顯然其靈活性不夠高。而infinite-tree是一個不受框架限制的無限級分類的包。
關於應用場景
之前發佈的tp5-nestedsets有人給我留言說一般來說分類最多也只有三級而三級分類用id和parent_id來約束就可以了何必要這種無限級的包呢?
首先這個包中對於分類這種具有樹關係的數據是以樹關係來描述的,查找,操作都是按照樹的操作方式,顯然比我們根據id和parent_id來遍歷要方便,而且高效。就拿簡單的來說。你查一個三級分類的頂級分類的所有後代分類最多需要查詢3次,遞歸也效率不高。但是使用本工具包一次就可以查完。其實現實中分類的層級還真的不止三級那麼簡單,我們所看到的三級一般都是創建了虛擬分類讓用戶感受到就是三級分類,事實上比這複雜得多,也可能不是這個包就能解決的問題,可能會用到特徵值,搜索等。
使用
安裝
composer require gmars/infinite-tree
如果還沒有使用composer請查看composer安裝的相關教程
實例化InfiniteTree
因爲要脫離具體框架所以數據庫配置需要自己配置,建議大家使用時可以再稍作封裝,當然不封裝也可以
//這一部分是數據庫配置數組
$dbConfig = [
'hostname' => '127.0.0.1',
'username' => 'root',
'password' => 'root',
'database' => 'test',
'hostport' => 3306
];
//這一部分是數據表中的鍵配置如果和默認一致可以不用配置
$keyConfig = [
'left_key' => 'left_key',
'right_key' => 'right_key',
'level_key' => 'level',
'primary_key' => 'id',
'parent_key' => 'parent_id'
];
$infiniteTree = new InfiniteTree('tree', $dbConfig, $keyConfig);
上邊實例化時第一個參數是你的表名,第二個是數據庫配置,第三個是鍵對應關係
創建數據表
如果你還沒有創建數據表調用此方法會創建一個數據表。具體其他需要的字段你可以在創建完手動添加。
$infiniteTree->checkTable()
方法說明
1.獲取整個樹結構
$infiniteTree->getTree()
2.獲取$id的所有後代節點(不包含自己)
$infiniteTree->getBranch($id)
3.獲取$id的所有子節點(注意只是子節點)
$infiniteTree->getChildren($id)
4.獲取節點$id及所有的後代節點
$infiniteTree->getPath($id)
5.在節點id的所有子節點最後(bottom)插入如果第三個參數是top就是在所有子節點之前插入
$infiniteTree->insert($id, ['name' => '測試節點'], 'bottom')
6.把id爲8的節點移動到id爲1的節點下
$infiniteTree->moveUnder(8, 1)
7.把id爲8的節點移動到id爲2的節點前面(before)如果要移到後邊是after
$infiniteTree->moveNear(8, 2, "before")
注意事項
- 在之前的版本中getItem有加對象中的靜態緩存,感覺沒有必要這個版本中沒有加,緩存請自行控制
- 關於左值和右值來定義樹的知識如果不是很清楚請先查閱相應的資料
- 如果有任何問題請提issue
- 如果感覺用着很方便就star一下