計(jì)算機(jī)中的樹【附有示例:JS實(shí)現(xiàn)的文件管理器】

自然界的樹,不需要我來(lái)解釋。不過(guò)對(duì)于計(jì)算機(jī)中的樹結(jié)構(gòu),可難倒了一大片人。其實(shí),我們?nèi)粘I钪袝?huì)經(jīng)常接觸到樹結(jié)構(gòu)。

比如,一個(gè)公司的結(jié)構(gòu)劃分。公司最大的當(dāng)然是老板了,老板將公司劃分為多個(gè)大部門,各個(gè)大部門下又劃分為多個(gè)小部門,而每個(gè)小部門內(nèi)部還可能會(huì)劃分為多個(gè)小組,組內(nèi)還會(huì)根據(jù)工作種類的不同劃分為不同的崗位。這里,如果需要查詢這個(gè)公司總共有多少崗位,你會(huì)怎么辦?想想都頭大啊。

再比如,我們計(jì)算機(jī)中存儲(chǔ)文件的時(shí)候,一個(gè)文件夾下面有多個(gè)子文件夾,而這些子文件夾還會(huì)有其他子子文件夾,還可能會(huì)有更多。如果我們要找某個(gè)文件,在不知道它在哪個(gè)文件夾的時(shí)候,我們會(huì)使用計(jì)算機(jī)的搜索功能。那么,計(jì)算機(jī)如何來(lái)實(shí)現(xiàn)這個(gè)功能呢?想想都頭大啊。

這篇文章就來(lái)介紹一下我用JS實(shí)現(xiàn)的樹結(jié)構(gòu)及其常用操作。

介紹

了解一點(diǎn)計(jì)算機(jī)的朋友都知道,樹是由一堆具有相同結(jié)構(gòu)的元素嵌套而來(lái)的。它根據(jù)不同的特性分為很多的種類,比如二叉樹,滿樹,紅黑樹,B樹,B+樹等等等。而樹的嵌套結(jié)構(gòu)就決定了,它相關(guān)的算法多數(shù)可以通過(guò)遞歸來(lái)解決,比如二叉樹的先序遍歷、中序遍歷等。

廢話不多說(shuō)了,直接來(lái)看看實(shí)現(xiàn)代碼吧。

實(shí)現(xiàn)一棵樹

一棵樹最重要的東西就是它的節(jié)點(diǎn)了,包括當(dāng)前節(jié)點(diǎn)及其子節(jié)點(diǎn),另外還需要一個(gè)ID來(lái)唯一標(biāo)識(shí)每一個(gè)節(jié)點(diǎn)。所以我這里的節(jié)點(diǎn)如下:

var nodeID = 1;

Ycc.Tree = function() {

   /**
    * 節(jié)點(diǎn)的自增ID,不允許修改,且每個(gè)對(duì)象必須唯一
    * @type {number}
    */
   this.$id = nodeID++;

   /**
    * 節(jié)點(diǎn)的父節(jié)點(diǎn)ID,不允許修改
    * @type {null|Ycc.Tree}
    */
   this.$parentID = null;

   /**
    * 節(jié)點(diǎn)的子節(jié)點(diǎn)列表
    * @type {Array}
    */
   this.children = [];

   /**
    * 節(jié)點(diǎn)所攜帶的數(shù)據(jù)
    * @type {any}
    */
   this.data = null;

};

上面代碼中Ycc為一個(gè)全局變量,是我真正做的一個(gè)項(xiàng)目名,可以忽略。

可以看到,這里除了自己的ID外,還使用了parentID,這樣在尋找父節(jié)點(diǎn)的時(shí)候會(huì)非常方便。

另外,借助js的靈活性,直接使用children數(shù)組表示當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)。

如果我告訴你,一棵樹我們就寫完了,你會(huì)感到驚訝嗎?

事實(shí)確實(shí)如此,樹是一個(gè)集合的概念,集合內(nèi)元素的結(jié)構(gòu)就決定了樹的結(jié)構(gòu)。只是,如果這樣我們就需要手動(dòng)的去設(shè)置ID和parentID,以此來(lái)保證它們的嵌套。

所以,為了豐富一棵樹,我們還需要新增一些便利的方法,來(lái)保證樹的特性,而不用手動(dòng)去設(shè)置。

給樹添加常用的操作

操作一:根據(jù)json初始化一棵樹

在js及其他語(yǔ)言中,最常用的數(shù)據(jù)結(jié)構(gòu)莫過(guò)于json了。比如,如下結(jié)構(gòu)是示例中的一個(gè)json,我們會(huì)根據(jù)它來(lái)生成對(duì)應(yīng)的樹。

{
   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"
            },
         ]
      }
   ]
}

這里的實(shí)現(xiàn)代碼如下:

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行,新建了一個(gè)root,表示這棵樹的根節(jié)點(diǎn),并在第3行將json中的數(shù)據(jù)附加給節(jié)點(diǎn)。

第4行判斷節(jié)點(diǎn)是否有子節(jié)點(diǎn),如果有子節(jié)點(diǎn),在第5~7行我們遞歸調(diào)用方法createByJSON來(lái)為每一個(gè)子節(jié)點(diǎn)創(chuàng)建一顆子樹,并調(diào)用addChildTree方法添加到我們的root。

最后返回了我們的樹根。

這個(gè)addChildTree方法,我們暫時(shí)只需知道,它的作用是給當(dāng)前節(jié)點(diǎn)添加一顆子樹,稍后給出其實(shí)現(xiàn)。

可以看出,這段代碼很明顯的使用了分治算法策略。

即,將我們的問(wèn)題分解成多個(gè)子問(wèn)題,子問(wèn)題使用相同的解決方法(createByJSON),待子問(wèn)題解決完之后,將子問(wèn)題合并(addChildTree),即可解決我們的原問(wèn)題。

操作二:添加一顆子樹

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;
};

添加子樹,實(shí)質(zhì)上只是添加一個(gè)節(jié)點(diǎn)ID的引用,即ID和parentID之間的關(guān)聯(lián)關(guān)系。

上面代碼中,第2行判斷了樹根,第3~4行就是在操作它們的關(guān)聯(lián)關(guān)系。

操作三:獲取樹的最大深度

樹的最大深度是指,從樹根開始向下,總共有多少層級(jí)。

比如,我們文章開頭,對(duì)于公司示例,因?yàn)橛锌赡苡械拇蟛块T不劃分小部門,這個(gè)最大深度是指公司最多劃分了幾層。

對(duì)于文件夾示例,因?yàn)橛械奈募A可能為空,這個(gè)最大深度是指文件夾最多能點(diǎn)到哪兒。

實(shí)現(xiàn)代碼如下:

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;
};

這個(gè)地方也用到了分治策略。

第4行判斷有沒(méi)子級(jí),第5~7行求出子級(jí)的最大深度,將其加1與當(dāng)前最大深度做比較,取最大值即為我們所求的最大深度。

操作四:樹的遍歷

我總共寫了四種遍歷方法。分別是:普通遍歷 左子樹優(yōu)先遍歷 右子樹優(yōu)先遍歷 按層級(jí)向下遍歷。

這里貼一個(gè)普通遍歷,感興趣的可以看看其他的遍歷方式。

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

總結(jié)

對(duì)于程序員,樹結(jié)構(gòu)是經(jīng)常遇到的,我們的HTML文檔就是一棵樹,xml文件也是一棵樹。

各個(gè)省市縣也可以構(gòu)成一顆樹,淘寶京東的欄目分類也是一棵樹,任何有嵌套結(jié)構(gòu)的數(shù)據(jù)都可以構(gòu)成一棵樹。

所以,掌握樹相關(guān)的算法是很有必要的。

最后附一個(gè)示例,是我實(shí)現(xiàn)的一個(gè)模擬windows的文件管理器,點(diǎn)開鏈接看看吧。

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

?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一些概念 數(shù)據(jù)結(jié)構(gòu)就是研究數(shù)據(jù)的邏輯結(jié)構(gòu)和物理結(jié)構(gòu)以及它們之間相互關(guān)系,并對(duì)這種結(jié)構(gòu)定義相應(yīng)的運(yùn)算,而且確保經(jīng)過(guò)這...
    Winterfell_Z閱讀 6,584評(píng)論 0 13
  • B樹的定義 一棵m階的B樹滿足下列條件: 樹中每個(gè)結(jié)點(diǎn)至多有m個(gè)孩子。 除根結(jié)點(diǎn)和葉子結(jié)點(diǎn)外,其它每個(gè)結(jié)點(diǎn)至少有m...
    文檔隨手記閱讀 13,666評(píng)論 0 25
  • 1 序 2016年6月25日夜,帝都,天下著大雨,拖著行李箱和同學(xué)在校門口照了最后一張合照,搬離寢室打車去了提前租...
    RichardJieChen閱讀 5,372評(píng)論 0 12
  • Given an array where elements are sorted in ascending ord...
    Eazow閱讀 428評(píng)論 0 0
  • 我們都知道,自卑也是一種負(fù)能量。每個(gè)人都想擺脫自卑帶來(lái)的困擾,我也是因此才學(xué)了心理學(xué)這門課程。 首先,我們要明確一...
    龍航007閱讀 3,297評(píng)論 0 1

友情鏈接更多精彩內(nèi)容