前言
最近接到一個關於樹的需求,想起了大學那會兒聽過俞敏洪有關樹的演講,老師重複在課上放了很多次,N年過去了印象還是很深。
演講內容是介樣的,讓我們一起來喝湯~
人的生活方式有兩種,
第一種方式是像草一樣活着,
你儘管活着,
每年還在成長,
但是你畢竟是一棵草,
你吸收雨露陽光,
但是卻長不大。
人們可以踩過你,
但是人們不會因爲你的痛苦,而他產生痛苦;
人們不會因爲你被踩了,而來憐憫你,
因爲人們本身就沒有看到你。
所以我們每一個人,
都應該像樹一樣的成長,
即使我們現在什麼都不是,
但是隻要你有樹的種子,
即使你被踩到泥土中間,
你依然能夠吸收泥土的養分,
自己成長起來。
當你長成參天大樹以後,
遙遠的地方,人們就能看到你;
走近你,你能給人一片綠色。
活着是美麗的風景,
死了依然是棟樑之才,
活着死了都有用。
需求是這樣的:
構造無限級樹型結構,界面類似下邊這樣的結構
然後,還需要把所有的橙色標記的葉子節點從垂直方向篩選出來,最終效果就是得到一個這樣順序的一級數組
KKK
QQQ
JJJ
HHH
GGG
LLL
需要根據AAA這個節點和他所有子節點的數據,構造出一個無限級樹型結構,如下
數據
- 頂級AAA數據
{"id":348019,"name":"AAA","is_leaf":2,"parent_id":347234,"type":0,"order":["348020"]}
- AAA所有的孩子節點
[{"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;
}
我們來使用一下,使用如下
$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);
echo json_encode($topNode);
最終輸出的json如下
可以看到,已經構造出了一個無限級的樹,並且和我們的界面是一模一樣的層級和順序
拉下來現在我們要遍歷這棵樹,爲了獲取到所有is_leaft=1的節點,我們先用廣度優先遍歷來試一下,因爲這個對於找節點來說性能比較高
廣度優先遍歷
代碼如下
private function bfsFindLeafs($node)
{
if (empty($node['children'])) {
return [];
}
$result = [];//滿足條件的組合
$q = [];
$q[] = $node['children'][0];
while (count($q) > 0) {
$size = count($q);
for ($i = 0; $i < $size; $i++) {
$current = array_shift($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": 348033,
"name": "KKK",
"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": 348034,
"name": "LLL",
"is_leaf": 1,
"parent_id": 348020,
"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": 348031,
"name": "QQQ",
"is_leaf": 1,
"parent_id": 348025,
"type": 11,
"order": [],
"children": []
}
]
好,我們已經可以獲取到所有is_leaf爲1的節點了,但是這裏有一個問題,不是從上到下輸出的,是最近輸出的
KKK->GGG->LLL->JJJ->HHH->QQQ
其實這就是因爲廣度優先遍歷是最近輸出的,爲了從上到下輸出,我們需要深度優先遍歷來實現了
深度優先遍歷
深度優先遍歷其實就是回溯算法的一種,爲了從上到下輸出,需要先序遍歷
/**
* 獲取所有的葉子路徑(深度度優先遍歷)
*/
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)
{
if (empty($node['children'])) {
return [];
}
$result = [];//滿足條件的組合
$q = [];
$q[] = $node['children'][0];
while (count($q) > 0) {
$size = count($q);
for ($i = 0; $i < $size; $i++) {
$current = array_shift($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~