zoukankan      html  css  js  c++  java
  • 构造无限级树并深度遍历查找指定节点

    前言

    最近接到一个关于树的需求,想起了大学那会儿听过俞敏洪有关树的演讲,老师重复在课上放了很多次,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":"MMMu65e0u53f6u5b50","is_leaf":2,"parent_id":348021,"type":0,"order":[]},{"id":348036,"name":"NNNu65e0u53f6u5b50","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":"MMMu65e0u53f6u5b50","is_leaf":2,"parent_id":348021,"type":0,"order":[]},{"id":348036,"name":"NNNu65e0u53f6u5b50","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":"MMMu65e0u53f6u5b50","is_leaf":2,"parent_id":348021,"type":0,"order":[]},{"id":348036,"name":"NNNu65e0u53f6u5b50","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~

  • 相关阅读:
    Java容器-引用分类与部分Map用法
    Java容器-引用数据类型排序+TreeSet、TreeMap底层实现
    JAVA容器-重点总结与深度解析
    JAVA容器-浅谈HashMap的实现原理
    JAVA容器-模拟LinkedList实现(双链表)
    JAVA容器-模拟ArrayList的底层实现
    在代码动态设置RelativeLayout的属性,比如layout_below
    java substring
    String StringBuffer StringBuilder
    线性布局通过适配器可以动态加载view
  • 原文地址:https://www.cnblogs.com/chenqionghe/p/14477221.html
Copyright © 2011-2022 走看看