1. 程式人生 > >JavaScript 構造樹形結構的一種高效演算法

JavaScript 構造樹形結構的一種高效演算法

引言

我們經常會碰到樹形資料結構,比如組織層級、省市縣或者動植物分類等等資料。下面是一個樹形結構的例子:

在實際應用中,比較常見的做法是將這些資訊儲存為下面的結構,特別是當存在1對多的父/子節點關係時:

const data = [
  { id: 56, parentId: 62 },
  { id: 81, parentId: 80 },
  { id: 74, parentId: null },
  { id: 76, parentId: 80 },
  { id: 63, parentId: 62 },
  { id: 80, parentId: 86 },
  { id: 87, parentId: 86 },
  { id: 62, parentId: 74 },
  { id: 86, parentId: 74 },
];

那麼,如何將這種物件陣列格式轉換為層級樹的格式呢?其實,利用 JavaScript 物件引用的特性,實現起來會非常簡單。它可以不用遞迴,在O(n)時間內完成。

術語

為了表述方便,我們先來定義幾個術語。我們把陣列中的每個元素(也就樹形圖裡的每個圓圈)稱為“節點”。節點可以是多個節點的“父節點”,也可以是某個節點的“子節點”。上圖中,節點 86節點 80 和節點 87的“父節點”,節點 86 是節點 74的子節點。樹的最頂部節點稱為“根節點”。

思路

為了構造樹形結構,我們需要:

  1. 遍歷data陣列
  2. 找到當前元素的父元素
  3. 在父元素物件上新增一個對該子元素的引用
  4. 元素如果沒有父元素,那我們就認為它是樹的根節點

我們可以看到到,引用被儲存在物件樹下,這就是為什麼我們可以在O(n)時間內完成這個任務!

建立 ID-陣列索引對映關係

雖然不是必需的,但是這個對映關係可以幫我們快速找到元素的位置,方便找到到父元素的引用。

const idMapping = data.reduce((acc, el, i) => {
  acc[el.id] = i;
  return acc;
}, {});

對映結果如下,後面你會看到它的用處有多大:

{
  56: 0,
  62: 7,
  63: 4,
  74: 2,
  76: 3,
  80: 5,
  81: 1,
  86: 8,
  87: 6,
};

構造樹形結構

現在我們開始構造這個樹形結構。遍歷這個物件陣列,找到每個元素的父元素物件,然後新增對這個元素的引用。現在你應該看到了,這個 idMapping用來定位元素的位置多麼方便(常數時間)。

let root;
data.forEach(el => {
  // 判斷根節點
  if (el.parentId === null) {
    root = el;
    return;
  }
  // 用對映表找到父元素
  const parentEl = data[idMapping[el.parentId]];
  // 把當前元素新增到父元素的`children`陣列中
  parentEl.children = [...(parentEl.children || []), el];
});

完事!用console.log 列印 root 看下:

console.log(root);
{
  id: 74,
  parentId: null,
  children: [
    {
      id: 62,
      parentId: 74,
      children: [{ id: 56, parentId: 62 }, { id: 63, parentId: 62 }],
    },
    {
      id: 86,
      parentId: 74,
      children: [
        {
          id: 80,
          parentId: 86,
          children: [{ id: 81, parentId: 80 }, { id: 76, parentId: 80 }],
        },
        { id: 87, parentId: 86 },
      ],
    },
  ],
};

原理

為什麼可以這麼做呢?這是因為,data 數組裡的每個元素都是記憶體裡的一個物件引用, forEach迴圈裡的el變數其實是指向記憶體裡的一個物件,parentEl也引用了一個物件。

如果記憶體中的一個物件引用了一個 children 陣列,這些子元素同樣可以引用自己的子元素陣列,這些關聯關係都是通過引用完成的。

總結

物件引用是 JavaScript 中最基本的概念之一,需要更多的學習和理解。真正理解這個概念後,既可以避免棘手的 bug,又可以為看似複雜的問題提供相對簡單的解決方案。

歡迎關注微信公眾號:1024譯站

相關推薦

JavaScript 構造樹形結構高效演算法

引言 我們經常會碰到樹形資料結構,比如組織層級、省市縣或者動植物分類等等資料。下面是一個樹形結構的例子: 在實際應用中,比較常見的做法是將這些資訊儲存為下面的結構,特別是當存在1對多的父/子節點關係時: const data = [ { id: 56, parentId: 62 }, { id:

【資料結構】十排序演算法C++實現

   練習了十一種排序演算法的C++實現:以下依次為,冒泡、選擇、希爾、插入、二路歸併、快排、堆排序、計數排序、基數排序、桶排序,可建立sort.h和main.cpp將程式碼放入即可執行。如有錯誤,請指出更正,謝謝交流。 // sort.h # include <

javascript將扁平的資料轉為樹形結構 O(n)級演算法

當我們需要將一個一維陣列轉換成一個多層結構的時候,最簡單但是最慢的就是多個for迴圈巢狀,但是這樣做有一些缺點,那就是效率太低、而且有多少層就需要巢狀幾個for迴圈,不好用。 我實現了用O(n)級演算法將 一個扁平的陣列即一維陣列代表的選單結構轉換成一個多層級的選單結構。 一位陣列中

javascript將扁平的資料轉為樹形結構 O(n)級演算法

當我們需要將一個一維陣列轉換成一個多層結構的時候,最簡單但是最慢的就是多個for迴圈巢狀,但是這樣做有一些缺點,那就是效率太低、而且有多少層就需要巢狀幾個for迴圈,不好用。 我實現了用O(n)級演算法將 一個扁平的陣列即一維陣列代表的選單結構轉換成一個多層級的選單結構。

高效的QPS統計方法

循環數組 eset 指數 yun return hashmap await spa 全面 一、概述 對QPS的統計,通常是對一秒內各線程通過數據處理鏈中某一個切入點的次數進行累加計數。且不論采用何種方式都繞不開鎖,那如何結合QPS統計的場景,減少線程之間對鎖的競爭,是各實現

數獨問題的簡單演算法程式碼實現

五一期間無聊時想起去年考研複試有一道上機題目當時沒作出來,於是一時興起想重新拾起看看是當時太緊張,還是自己能力不足。然後發現這道題目還真稍微有些難度,相當於一道數獨問題(sudoku)的簡化版。自己想來想去也只能想到兩種演算法,一種是拿剩餘元素做全排列測試,一種是回溯法測試。最後只實現了一個全排

[論文學習]An Effective Approach for Mining Mobile User Habits:高效挖掘移動使用者習慣的方法

原文: Cao H, Bao T, Yang Q, et al. An effective approach for mining mobile user habits[C]//Proceedings of the 19th ACM international confere

高效的序列化方式——MessagePack

最近在弄一些資料分析方面的內容,發現很多時候資料瓶頸在模組之間的資料序列化和反序列化上了,原來專案中用的是Json,找了一圈發現Json.net在Json序列化庫中已經是效能的佼佼者了,便準備從序列化方式入手了,最後選擇了MessagePack的這個序列化的庫。 MessagePack是一種的序列化格式。這

JavaScript預解釋是毫無節操的機制

前言 JavaScript是一門解釋型的語言 , 想要執行JavaScript程式碼需要兩個階段 編譯階段: 編譯階段就是我們常說的JavaScript預解釋(預處理)階段,在這個階段JavaScript直譯器將完成把JavaScript指令碼程式碼轉換到位元組碼 執行階段: 在編譯階段Java

ECMA:JavaScript中AOP的實現

相比Javascript,果然還是Java好用的多。 隨著JavaScript的發展,目前的JS已經支援Class等多種新的關鍵字,當然,也就多了很多新的坑。 今天發現某個專案中,突然出現了很多JWT異常,觀察了一下,JWT不知道為啥變成了null,不知道是那個前輩搞的,如果在發出請求之

【轉載】無鎖環形佇列的高效實現

1.環形佇列是什麼  佇列是一種常用的資料結構,這種結構保證了資料是按照“先進先出”的原則進行操作的,即最先進去的元素也是最先出來的元素.環形佇列是一種特殊的佇列結構,保證了元素也是先進先出的,但與一般佇列的區別是,他們是環形的,即佇列頭部的上個元素是佇列尾部,通常是容

高效的android雙擊退出(可擴充套件多擊)

參考Google,安卓手機中在檢視安卓系統版本的地方,三擊或者多擊會出現彩蛋,可以借鑑其原始碼進行實現。 //利用陣列來儲存時間     long[] mHits = new long[3];     @Override     pub

[資料結構]七排序演算法小結

氣泡排序 選擇排序 插入排序 歸併排序 堆排序 希爾排序 氣泡排序 時間複雜度o(n2)。下面以一個例子來看什麼是氣泡排序。 例: 第一次取區間[0,7],通過比較第i個和第i+1個的大小,如果第i+1個數字小於第i個,則互換。

資料結構排序演算法(動圖演示)

0,演算法概述 0.1演算法分類 十種常見排序演算法可以分為兩大類: 非線性時間比較類排序:通過比較來決定元素間的相對次序,由於其時間複雜度不能突破O(nlogn),因此稱為非線性時間比較類排序。 線性時間非比較類排序:不通過比較來決定元素間的相對次序,它可以突破基於比較排

高效雙端佇列(連結串列)實現方式

本文將介紹一種高效的雙端佇列(連結串列),包括資料結構、新建佇列、插入頭結點、插入尾節點、刪除頭結點等,所有操作的時間複雜度均為O(1)。 為了方便理解,部分函式前面均配了一幅圖作為介紹。 首先,資料結構定義如下: typedef struct node_s {

Liv Wild:高效的專案啟動方式——QuickStart

一種高效的專案啟動方式——QuickStart,該方法用以確認在專案開始之前理解專案的關鍵驅動因素。來自ThoughtWorks英國分公司的Liv Wild為現場觀眾介紹QuickStart的理念與實踐。 主持人:有請Liv Wild做主題演講,演講的題目是“一種高效的專案啟

高效熵提取的TRNG設計

        在如今人工智慧與虛擬互動的時代,人們對​​於資訊保安有了更高的要求。隨機數作為資訊加密與通訊協議中最為基本的一環,其隨機性與不可預測性有了更高的標準。儘管人們可以通過計算機,利用演算法產生的偽隨機數來作為隨機源,但隨著新技術新科技的發展,在資訊保安領域隨機數的質量也成為了一個潛在的隱患。因為通

高效無鎖記憶體佇列的實現

Disruptor是LMAX公司開源的一個高效的記憶體無鎖佇列。這兩天看了一下相關的設計文件和部落格,下面嘗試進行一下總結。 第一部分。引子 談到併發程式設計,有幾個概念是避免不了的。 1.鎖:鎖是用來做併發最簡單的方式,當然其代價也是最高的。核心態的鎖的時候需要作業

高效的跨程序MVC架構

VcSmith、VcTester、VcAuto基同於一體系架構,功能層次劃分清晰,很好的實現了元件重用,使用一種跨程序MVC結構,保證系統具良好的伸縮性,構造出一種軟性的、指令碼化的緩衝層,讓調測變得很便利而高效。 VcTester分層架構 一個高效的軟體開發系統應有良好的

ORACLE刪除重複資料的高效的方法

刪除重複資料的一種高效的方法 表demo是重複拷貝自dba_objects,有88萬左右,不重複的是27323,沒有索引 方法一:delete   from   demo   a   where   a.rowid   <>   (select   max(