前言

最近接到一个关于树的需求,想起了大学那会儿听过俞敏洪有关树的演讲,老师重复在课上放了很多次,N年过去了印象还是很深。
演讲内容是介样的,让我们一起来喝汤~

回到今天的主题,需求是这样的:构造无限级树型结构,界面类似下边这样的结构
构造无限级树并深度遍历查找指定节点-LMLPHP

然后,还需要把所有的橙色标记的叶子节点从垂直方向筛选出来,最终效果就是得到一个这样顺序的一级数组

KKK
QQQ
JJJ
HHH
GGG
LLL

需要根据AAA这个节点和他所有子节点的数据,构造出一个无限级树型结构,如下
构造无限级树并深度遍历查找指定节点-LMLPHP

数据

  • 顶级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;
    }

我们来使用一下,使用如下


最终输出的json如下
构造无限级树并深度遍历查找指定节点-LMLPHP
构造无限级树并深度遍历查找指定节点-LMLPHP

可以看到,已经构造出了一个无限级的树,并且和我们的界面是一模一样的层级和顺序

拉下来现在我们要遍历这棵树,为了获取到所有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~,和我们之前的界面垂直顺序一致
构造无限级树并深度遍历查找指定节点-LMLPHP

完整代码

下面给出完整代码

<?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~

03-04 07:19