美文网首页生学前端-团队博客生学教育-团队博客
计算机中的树【附有示例:JS实现的文件管理器】

计算机中的树【附有示例:JS实现的文件管理器】

作者: 励志前端小黑哥 | 来源:发表于2018-08-15 17:37 被阅读12次

    自然界的树,不需要我来解释。不过对于计算机中的树结构,可难倒了一大片人。其实,我们日常生活中会经常接触到树结构。

    比如,一个公司的结构划分。公司最大的当然是老板了,老板将公司划分为多个大部门,各个大部门下又划分为多个小部门,而每个小部门内部还可能会划分为多个小组,组内还会根据工作种类的不同划分为不同的岗位。这里,如果需要查询这个公司总共有多少岗位,你会怎么办?想想都头大啊。

    再比如,我们计算机中存储文件的时候,一个文件夹下面有多个子文件夹,而这些子文件夹还会有其他子子文件夹,还可能会有更多。如果我们要找某个文件,在不知道它在哪个文件夹的时候,我们会使用计算机的搜索功能。那么,计算机如何来实现这个功能呢?想想都头大啊。

    这篇文章就来介绍一下我用JS实现的树结构及其常用操作。

    介绍

    了解一点计算机的朋友都知道,树是由一堆具有相同结构的元素嵌套而来的。它根据不同的特性分为很多的种类,比如二叉树,满树,红黑树,B树,B+树等等等。而树的嵌套结构就决定了,它相关的算法多数可以通过递归来解决,比如二叉树的先序遍历、中序遍历等。

    废话不多说了,直接来看看实现代码吧。

    实现一棵树

    一棵树最重要的东西就是它的节点了,包括当前节点及其子节点,另外还需要一个ID来唯一标识每一个节点。所以我这里的节点如下:

    var nodeID = 1;
    
    Ycc.Tree = function() {
    
       /**
        * 节点的自增ID,不允许修改,且每个对象必须唯一
        * @type {number}
        */
       this.$id = nodeID++;
    
       /**
        * 节点的父节点ID,不允许修改
        * @type {null|Ycc.Tree}
        */
       this.$parentID = null;
    
       /**
        * 节点的子节点列表
        * @type {Array}
        */
       this.children = [];
    
       /**
        * 节点所携带的数据
        * @type {any}
        */
       this.data = null;
    
    };
    

    上面代码中Ycc为一个全局变量,是我真正做的一个项目名,可以忽略。

    可以看到,这里除了自己的ID外,还使用了parentID,这样在寻找父节点的时候会非常方便。

    另外,借助js的灵活性,直接使用children数组表示当前节点的子节点。

    如果我告诉你,一棵树我们就写完了,你会感到惊讶吗?

    事实确实如此,树是一个集合的概念,集合内元素的结构就决定了树的结构。只是,如果这样我们就需要手动的去设置ID和parentID,以此来保证它们的嵌套。

    所以,为了丰富一棵树,我们还需要新增一些便利的方法,来保证树的特性,而不用手动去设置。

    给树添加常用的操作

    操作一:根据json初始化一棵树

    在js及其他语言中,最常用的数据结构莫过于json了。比如,如下结构是示例中的一个json,我们会根据它来生成对应的树。

    {
       data:'/',
       children:[
          {
             data:"a",
             children:[
                {
                   data:"d",
                   children:[
                      {
                         data:"g"
                      },
                      {
                         data:"h"
                      },
                      {
                         data:"i"
                      },
                   ]
                },
                {
                   data:"e",
                   children:[
                      {
                         data:"j"
                      },
                      {
                         data:"k"
                      },
                      {
                         data:"l"
                      },
                   ]
                },
                {
                   data:"f"
                },
             ]
          },
          {
             data:"b",
             children:[
                {
                   data:"m"
                },
                {
                   data:"n"
                },
             ]
          },
          {
             data:"c",
             children:[
                {
                   data:"o"
                },
                {
                   data:"p"
                },
                {
                   data:"q"
                },
             ]
          }
       ]
    }
    

    这里的实现代码如下:

    Ycc.Tree.createByJSON = function (json) {
       var root = new Ycc.Tree();
       root.data = json.data;
       if(Ycc.utils.isArray(json.children) && json.children.length>0){
          for(var i=0;i<json.children.length;i++){
             root.addChildTree(Ycc.Tree.createByJSON(json.children[i]));
          }
       }
       return root;
    };
    

    上面代码,第2行,新建了一个root,表示这棵树的根节点,并在第3行将json中的数据附加给节点。

    第4行判断节点是否有子节点,如果有子节点,在第5~7行我们递归调用方法createByJSON来为每一个子节点创建一颗子树,并调用addChildTree方法添加到我们的root。

    最后返回了我们的树根。

    这个addChildTree方法,我们暂时只需知道,它的作用是给当前节点添加一颗子树,稍后给出其实现。

    可以看出,这段代码很明显的使用了分治算法策略。

    即,将我们的问题分解成多个子问题,子问题使用相同的解决方法(createByJSON),待子问题解决完之后,将子问题合并(addChildTree),即可解决我们的原问题。

    操作二:添加一颗子树

    Ycc.Tree.prototype.addChildTree = function (tree) {
       if(tree.$parentID) return console.error("sub tree's parent has exist! can't add!",tree);
       tree.$parentID = this.$id;
       this.children.push(tree);
       return this;
    };
    

    添加子树,实质上只是添加一个节点ID的引用,即ID和parentID之间的关联关系。

    上面代码中,第2行判断了树根,第3~4行就是在操作它们的关联关系。

    操作三:获取树的最大深度

    树的最大深度是指,从树根开始向下,总共有多少层级。

    比如,我们文章开头,对于公司示例,因为有可能有的大部门不划分小部门,这个最大深度是指公司最多划分了几层。

    对于文件夹示例,因为有的文件夹可能为空,这个最大深度是指文件夹最多能点到哪儿。

    实现代码如下:

    Ycc.Tree.prototype.getDepth = function () {
       var self = this;
       var dep = 1;
       if(self.children.length>0){
          for(var i=0;i<self.children.length;i++){
             var subDep = self.children[i].getDepth();
             dep = subDep+1>dep?subDep+1:dep;
          }
       }
       return dep;
    };
    

    这个地方也用到了分治策略。

    第4行判断有没子级,第5~7行求出子级的最大深度,将其加1与当前最大深度做比较,取最大值即为我们所求的最大深度。

    操作四:树的遍历

    我总共写了四种遍历方法。分别是:普通遍历 左子树优先遍历 右子树优先遍历 按层级向下遍历。

    这里贴一个普通遍历,感兴趣的可以看看其他的遍历方式。

    Ycc.Tree.prototype.itor = function (option) {
       var self = this;
       function each(cb) {
          if(cb.call(self,self)) return true;
          if(self.children.length>0){
             for(var i=0;i<self.children.length;i++){
                if(self.children[i].itor().each(cb)) return true;
             }
          }
          return false;
       }
    }
    

    地址为:https://github.com/lizhiqianduan/ycc/blob/develop/src/Ycc.Tree.class.js

    总结

    对于程序员,树结构是经常遇到的,我们的HTML文档就是一棵树,xml文件也是一棵树。

    各个省市县也可以构成一颗树,淘宝京东的栏目分类也是一棵树,任何有嵌套结构的数据都可以构成一棵树。

    所以,掌握树相关的算法是很有必要的。

    最后附一个示例,是我实现的一个模拟windows的文件管理器,点开链接看看吧。

    http://www.lizhiqianduan.com/products/ycc/examples/tree/

    相关文章

      网友评论

        本文标题:计算机中的树【附有示例:JS实现的文件管理器】

        本文链接:https://www.haomeiwen.com/subject/ebwwbftx.html