最近接到一個關於樹的需求,想起了大學那會兒聽過俞敏洪有關樹的演講,老師重複在課上放了不少次,N年過去了印象仍是很深。
演講內容是介樣的,讓咱們一塊兒來喝湯~php
人的生活方式有兩種,
第一種方式是像草同樣活着,
你儘管活着,
每一年還在成長,
可是你畢竟是一棵草,
你吸取雨露陽光,
可是卻長不大。
人們能夠踩過你,
可是人們不會由於你的痛苦,而他產生痛苦;
人們不會由於你被踩了,而來憐憫你,
由於人們自己就沒有看到你。
因此咱們每個人,
都應該像樹同樣的成長,
即便咱們如今什麼都不是,
可是隻要你有樹的種子,
即便你被踩到泥土中間,
你依然可以吸取泥土的營養,
本身成長起來。
當你長成參天大樹之後,
遙遠的地方,人們就能看到你;
走近你,你能給人一片綠色。
活着是美麗的風景,
死了依然是棟樑之才,
活着死了都有用。node
回到今天的主題,需求是這樣的:構造無限級樹型結構,界面相似下邊這樣的結構
算法
而後,還須要把全部的橙色標記的葉子節點從垂直方向篩選出來,最終效果就是獲得一個這樣順序的一級數組數據庫
KKK QQQ JJJ HHH GGG LLL
須要根據AAA這個節點和他全部子節點的數據,構造出一個無限級樹型結構,以下
json
{"id":348019,"name":"AAA","is_leaf":2,"parent_id":347234,"type":0,"order":["348020"]}
[{"id":348020,"name":"BBB","is_leaf":2,"parent_id":348019,"type":0,"order":["348037","348033","348021","348023","348034"]},{"id":348021,"name":"CCC","is_leaf":2,"parent_id":348020,"type":0,"order":["348022","348035"]},{"id":348022,"name":"DDD","is_leaf":2,"parent_id":348021,"type":0,"order":["348024","348032","348030"]},{"id":348024,"name":"EEE","is_leaf":2,"parent_id":348022,"type":0,"order":["348025","348036"]},{"id":348025,"name":"FFF","is_leaf":2,"parent_id":348024,"type":0,"order":["348031"]},{"id":348030,"name":"HHH","is_leaf":1,"parent_id":348022,"type":11,"order":[]},{"id":348023,"name":"GGG","is_leaf":1,"parent_id":348020,"type":11,"order":[]},{"id":348031,"name":"QQQ","is_leaf":1,"parent_id":348025,"type":11,"order":[]},{"id":348032,"name":"JJJ","is_leaf":1,"parent_id":348022,"type":11,"order":[]},{"id":348035,"name":"MMM\u65e0\u53f6\u5b50","is_leaf":2,"parent_id":348021,"type":0,"order":[]},{"id":348036,"name":"NNN\u65e0\u53f6\u5b50","is_leaf":2,"parent_id":348024,"type":0,"order":[]},{"id":348033,"name":"KKK","is_leaf":1,"parent_id":348020,"type":11,"order":[]},{"id":348037,"name":"OOO","is_leaf":2,"parent_id":348020,"type":0,"order":[]},{"id":348034,"name":"LLL","is_leaf":1,"parent_id":348020,"type":11,"order":[]}]
節點數據庫結構都同樣,核心是有一個parent_id,每一個節點會保存直屬葉子的排序order數組
其實這裏考察的遞歸構造樹、廣度優先遍歷、深度優先遍歷
其實無論啥語言,思想都是這樣,由於這個需求是php系統寫的接口,就用php給出實現代碼,氣氛搞起來~性能
private function getTree($list, $node) { $tree = []; foreach ($list as $k => $v) { if ($v['parent_id'] == $node['id']) { $v['children'] = $this->getTree($list, $v); $tree[] = $v; } } if (empty($node['order'])) { return []; } //注意:爲了保證全部的葉子節點是根據已有的order排序,這裏咱們從新擺放一下 $treeMap = []; foreach ($tree as $v) { $treeMap[$v['id']] = $v;//創建id和數據的map } //根據order從新排序 $orderTree = []; foreach ($node['order'] as $id) {//根據order的順序從新擺放 $orderTree[] = $treeMap[$id]; } return $orderTree; }
咱們來使用一下,使用以下this
最終輸出的json以下
code
能夠看到,已經構造出了一個無限級的樹,而且和咱們的界面是如出一轍的層級和順序blog
拉下來如今咱們要遍歷這棵樹,爲了獲取到全部is_leaft=1的節點,咱們先用廣度優先遍從來試一下,由於這個對於找節點來講性能比較高
代碼以下
private function bfsFindLeafs($node) { $result = [];//知足條件的組合 $q = []; $q[] = $node; while (count($q) > 0) { $size = count($q); for ($i = 0; $i < $size; $i++) { $current = array_pop($q); //判斷節點若是是知足條件,加入路徑 if ($current['is_leaf'] == 1) { $result[] = $current; } //把全部的子節點加入隊列 foreach ($current['children'] as $v) { $q[] = $v; } } } return $result; }
咱們來使用一下
$getAllLeafs = $this->bfsFindLeafs($topNode); echo json_encode($getAllLeafs);
運行後輸出
[ { "id": 348034, "name": "LLL", "is_leaf": 1, "parent_id": 348020, "type": 11, "order": [], "children": [] }, { "id": 348023, "name": "GGG", "is_leaf": 1, "parent_id": 348020, "type": 11, "order": [], "children": [] }, { "id": 348030, "name": "HHH", "is_leaf": 1, "parent_id": 348022, "type": 11, "order": [], "children": [] }, { "id": 348032, "name": "JJJ", "is_leaf": 1, "parent_id": 348022, "type": 11, "order": [], "children": [] }, { "id": 348031, "name": "QQQ", "is_leaf": 1, "parent_id": 348025, "type": 11, "order": [], "children": [] }, { "id": 348033, "name": "KKK", "is_leaf": 1, "parent_id": 348020, "type": 11, "order": [], "children": [] } ]
好,咱們已經能夠獲取到全部is_leaf爲1的節點了,可是這裏有一個問題,不是從上到下輸出的
LLL GGG HHH JJJ QQQ KKK
這時候,咱們就須要一個深度優先遍從來實現了
深度優先遍歷其實就是回滾算法的一種,爲了從上到下輸出,須要先序遍歷
private function dfsFindLeafs($node) { $trackList = []; foreach ($node['children'] as $v) { if ($v['is_leaf'] == 1) { $trackList[] = $v; } if (empty($v['children'])) { continue; } $trackList = array_merge($trackList,$this->dfsFindLeafs($v)); } return $trackList; }
咱們再來運行一下
$getAllLeafs = $this->dfsFindLeafs($topNode); echo json_encode($getAllLeafs);
輸出
[ { "id": 348033, "name": "KKK", "is_leaf": 1, "parent_id": 348020, "type": 11, "order": [], "children": [] }, { "id": 348031, "name": "QQQ", "is_leaf": 1, "parent_id": 348025, "type": 11, "order": [], "children": [] }, { "id": 348032, "name": "JJJ", "is_leaf": 1, "parent_id": 348022, "type": 11, "order": [], "children": [] }, { "id": 348030, "name": "HHH", "is_leaf": 1, "parent_id": 348022, "type": 11, "order": [], "children": [] }, { "id": 348023, "name": "GGG", "is_leaf": 1, "parent_id": 348020, "type": 11, "order": [], "children": [] }, { "id": 348034, "name": "LLL", "is_leaf": 1, "parent_id": 348020, "type": 11, "order": [], "children": [] } ]
順序爲
KKK QQQ JJJ HHH GGG LLL
此次順序終於對了,oh yeah~,和咱們以前的界面垂直順序一致
下面給出完整代碼
<?php /** * Class Test * @author chenqionghe */ class Test { public function run() { $topNodeJson = '{"id":348019,"name":"AAA","is_leaf":2,"parent_id":347234,"type":0,"order":["348020"]}'; $childrenJson = '[{"id":348020,"name":"BBB","is_leaf":2,"parent_id":348019,"type":0,"order":["348037","348033","348021","348023","348034"]},{"id":348021,"name":"CCC","is_leaf":2,"parent_id":348020,"type":0,"order":["348022","348035"]},{"id":348022,"name":"DDD","is_leaf":2,"parent_id":348021,"type":0,"order":["348024","348032","348030"]},{"id":348024,"name":"EEE","is_leaf":2,"parent_id":348022,"type":0,"order":["348025","348036"]},{"id":348025,"name":"FFF","is_leaf":2,"parent_id":348024,"type":0,"order":["348031"]},{"id":348030,"name":"HHH","is_leaf":1,"parent_id":348022,"type":11,"order":[]},{"id":348023,"name":"GGG","is_leaf":1,"parent_id":348020,"type":11,"order":[]},{"id":348031,"name":"QQQ","is_leaf":1,"parent_id":348025,"type":11,"order":[]},{"id":348032,"name":"JJJ","is_leaf":1,"parent_id":348022,"type":11,"order":[]},{"id":348035,"name":"MMM\u65e0\u53f6\u5b50","is_leaf":2,"parent_id":348021,"type":0,"order":[]},{"id":348036,"name":"NNN\u65e0\u53f6\u5b50","is_leaf":2,"parent_id":348024,"type":0,"order":[]},{"id":348033,"name":"KKK","is_leaf":1,"parent_id":348020,"type":11,"order":[]},{"id":348037,"name":"OOO","is_leaf":2,"parent_id":348020,"type":0,"order":[]},{"id":348034,"name":"LLL","is_leaf":1,"parent_id":348020,"type":11,"order":[]}]'; $topNode = json_decode($topNodeJson, true); $childrenList = json_decode($childrenJson, true); $topNode['children'] = $this->getTree($childrenList, $topNode); $getAllLeafs = $this->dfsFindLeafs($topNode); echo json_encode($getAllLeafs); } /** * 遞歸構造無限級樹 * * @param $list * @param $node * @return array */ private function getTree($list, $node) { $tree = []; foreach ($list as $k => $v) { if ($v['parent_id'] == $node['id']) { $v['children'] = $this->getTree($list, $v); $tree[] = $v; } } if (empty($node['order'])) { return []; } //注意:爲了保證全部的葉子節點是根據已有的order排序,這裏咱們從新擺放一下 $treeMap = []; foreach ($tree as $v) { $treeMap[$v['id']] = $v;//創建id和數據的map } //根據order從新排序 $orderTree = []; foreach ($node['order'] as $id) {//根據order的順序從新擺放 $orderTree[] = $treeMap[$id]; } return $orderTree; } /** * 廣度優先遍歷獲取全部葉子 * @param $node * @return array */ private function bfsFindLeafs($node) { $result = [];//知足條件的組合 $q = []; $q[] = $node; while (count($q) > 0) { $size = count($q); for ($i = 0; $i < $size; $i++) { $current = array_pop($q); //判斷節點若是是知足條件,加入路徑 if ($current['is_leaf'] == 1) { $result[] = $current; } //把全部的子節點加入隊列 foreach ($current['children'] as $v) { $q[] = $v; } } } return $result; } /** * 獲取全部的葉子路徑(深度度優先遍歷) */ private function dfsFindLeafs($node) { $trackList = []; foreach ($node['children'] as $v) { if ($v['is_leaf'] == 1) { $trackList[] = $v; } if (empty($v['children'])) { continue; } $trackList = array_merge($trackList, $this->dfsFindLeafs($v)); } return $trackList; } } (new Test())->run();
ok,到這裏咱們就已經學會如何構造無限級樹型結構,並學會用深度優先遍歷垂直輸出指定條件節點,還知道了怎麼用廣度優先遍歷,giao~