巧妙利用引用,將數組轉換成樹形數組

来源:https://www.cnblogs.com/loveshes/archive/2019/11/17/11877799.html
-Advertisement-
Play Games

前言 筆者所做的一個項目需要做一個前端的樹形菜單,後端返回的數據是一個平行的list,list中的每個元素都是一個對象,例如 的值為 ,每個元素都指定了父元素,生成的菜單可以無限級嵌套。一開始找的插件需要手動將生成好的樹形數組傳進去才能使用(儘管後來找到了一個UI框架,可以直接傳list進去,只需要 ...


前言

筆者所做的一個項目需要做一個前端的樹形菜單,後端返回的數據是一個平行的list,list中的每個元素都是一個對象,例如list[0]的值為{id: 1, fid: 0, name: 一級菜單},每個元素都指定了父元素,生成的菜單可以無限級嵌套。一開始找的插件需要手動將生成好的樹形數組傳進去才能使用(儘管後來找到了一個UI框架,可以直接傳list進去,只需要指定id和fid),但是當時思索了好久都沒能正確寫出來,故在空下來的時候認真想了一下,整理成筆記,以便後期查閱。

準備工作

因為是前端處理,所以本文實現語言為js。

如下,有一個平行的list列表和一個不在list中的根節點root:

var s = [
  { id: 1, fid: 0, name: "第一級菜單1" },
  { id: 2, fid: 0, name: "第一級菜單2" },
  { id: 3, fid: 1, name: "第二級菜單1.1" },
  { id: 4, fid: 1, name: "第二級菜單1.2" },
  { id: 5, fid: 2, name: "第二級菜單2.1" },
  { id: 6, fid: 3, name: "第三級菜單1.1.1" },
  { id: 7, fid: 3, name: "第三級菜單1.1.2" },
  { id: 8, fid: 4, name: "第三級菜單1.2.1" },
  { id: 9, fid: 4, name: "第三級菜單1.2.2" },
  { id: 10, fid: 6, name: "第四級菜單1.1.1.1" },
  { id: 11, fid: 6, name: "第四級菜單1.1.1.2" },
  { id: 12, fid: 9, name: "第四級菜單1.2.2.1" },
  { id: 13, fid: 9, name: "第四級菜單1.2.2.2" },
  { id: 14, fid: 0, name: "第一級菜單3" }
]

var root = { id: 0, fid: 0, name: "根菜單" };

需要整理成類似於下麵的樣子,如果該節點沒有子節點,就沒有node屬性:

{
    id: xx,
    fid: xx,
    name: xx,
    node: [
        id: xx,
        fid: xx,
        name: xx,
        node: [...]
    ]
}

需要一個打亂list順序的shuffle演算法,該演算法會對原數組進行影響:

function shuffle(a) {
  var len = a.length;
  for (var i = 0; i < len; i++) {
    var end = len - 1;
    var index = (Math.random() * (end + 1)) >> 0;
    var t = a[end];
    a[end] = a[index];
    a[index] = t;
  }
};

使用JSON序列化來實現數組的深度拷貝:

function deepCopy(arr) {
  return JSON.parse(JSON.stringify(arr));
}

使用一個簡單的方式來初步判斷結果是否正確:

function check(node) {
    return JSON.stringify(node).match(/菜單/g).length;
}

使用遞歸

【思路】

對於這種問題,因為不知道到底要迴圈多少層,所以使用遞歸能夠以一種很方便的方式來解決。

【步驟】

1. 遍歷當前列表,找出fid為傳入的父元素的id的節點,並掛到父元素的node上;

2. 每找到一個節點就從當前列表刪除這個元素(不然遞歸怎麼終止);

3. 對於每一個子節點,重覆如上步驟,將子節點當成下一層的父節點繼續查找該節點的子節點。

可以看到,時間複雜度最壞為O(n!)

【實現】

function arr2tree(arr, father) {
  // 遍曆數組,找到當前father的所有子節點
  for (var i = 0; i < arr.length; i++) {   
    if (arr[i].fid == father.id) {
      // 這裡是有子節點才需要有node屬性(也就是說有node里絕不會為空list)
      if (!father.node) { 
        father.node = [];
      }
      var son = arr[i];
      father.node.push(son);
      arr.splice(i, 1); // 刪除該節點,當list為空的時候就終止遞歸
      i--; // 由於刪除了i節點,所以下次訪問的元素下標應該還是i
    }
  }
  // 再對每一個子節點進行如上操作
  if (father.node) { // 需要先判斷有沒有子節點
    var childs = father.node;
    for (var i=0; i<childs.length; i++) {
      arr2tree(arr, childs[i]); // 調用遞歸函數
    }
    // 用於按名稱進行排序,如果不強調順序可以去掉
    father.node.sort(function (a, b) {
      return a.name > b.name;
    })
  }
}

【檢驗】

shuffle(s); // 打亂數組
var arr = deepCopy(s); // 拷貝一份,避免對原數組進行修改
arr2tree(arr, root);
console.log(check(root)); // 預期輸出15
console.log(root); // 手工檢查輸出是否正確

不使用遞歸

【思路】

當數據量大的時候,使用遞歸及其容易因為記憶體溢出而無法運行,有沒有不使用遞歸的方式呢?能不能夠直接就用迴圈來搞定呢?能不能邊遍歷這個元素,就直接把這個元素放到正確的位置上,這樣就可以省好多事情。可以用一個哈希表(字典/對象)來儲存這些元素,鍵(屬性名)就是元素的id,這樣就可以直接判斷當前遍歷的元素的父元素在不在哈希表裡面了。

忽然,筆者想到了一個特性——引用js中的對象都是引用的,哪怕我已經把a對象push進一個list中了,我在後面對a對象進行的任何修改都會在list中反映出來。也就是說,我把a元素掛到對應的父元素f上了,當我在後面找到a元素的子元素b時,我把該子元素b掛到a上,f中掛載的a也會一樣有b元素。

【步驟】

1. 新建一個對象temp用於存放臨時信息。遍歷列表,將當前訪問元素a加到temp中(屬性名為對象id,屬性值為該對象);

2. 在temp中查找是否有a的子節點,有的話就將子節點掛到a上;

3. 在temp中查找是否有a的父節點,有的話就將a掛到父節點上;

可以看到,時間複雜度為O(n^2^),空間複雜度也不會太高,該方法不會對原數組進行修改。

【實現】

function arr2tree2(arr, root) {
  var temp = {};
  temp[root.id] = root;
  for (var i = 0; i < arr.length; i++) {
    // 插入一個新節點,後面對該節點的修改都會同步到該節點的父節點上
    temp[arr[i].id] = arr[i];
    // 查找是否有子節點
    var keys = Object.keys(temp);
    for (var j = 0; j < keys.length; j++) {
      if (temp[keys[j]].fid == arr[i].id) {
        temp[arr[i].id].node ? "" : temp[arr[i].id].node = [];
        temp[arr[i].id].node.push(temp[keys[j]]); // 將該子節點掛到當前節點的node上
      }
    }
    // 查找是否有父節點
    if (temp[arr[i].fid]) {
      temp[arr[i].fid].node ? "" : temp[arr[i].fid].node = [];
      temp[arr[i].fid].node.push(arr[i]); // 將當前節點掛到父節點的node上
    }
  }
  return temp;
}

【檢驗】

shuffle(s); // 打亂數組
var result = arr2tree2(s, root);
console.log(check(result[root.id])); // 預期輸出15
console.log(result[root.id]); // 手工檢查輸出是否正確

總結

平時筆者所做的項目大多也不涉及到演算法,平時秉承的也是能用迴圈解決的就用迴圈解決,可以看到,演算法對於程式員而言還是很重要的,本文也只是個人的想法,歡迎一起探討。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 做一個好看的登錄註冊界面 前言 最近在嘗試做網盤,使用的技術棧大概是 .net core + MVC + Mysql + Layui ,主要目的是通過這個具體的項目,熟悉熟悉 .net core 開發, .net 的未來就是他了! 我的設想 在完成後端的一部分 建設 之後,我把目光投向了前端——登陸 ...
  • 1.v-if和v-show v-if 和v-show都可以顯示和隱藏元素; 區別:(1)v-if初始值為false那麼這個元素不會被渲染 ,v-show不管初始值為何值都會被渲染 (2)v-if是控制DOM元素是否插入,v-show是控制css的display屬性 (3)v-if適合隱藏尚未載入的內 ...
  • 設置行高 由於簡單還是老樣子直接上代碼了哦,註意: 屬性值可以使用固定值如:20px..和百分比如:20%。 如果想讓文字垂直居中如下:行高的主要作用是用來設置文本的垂直方向居中對齊行高的值與高度的值一樣即可。 讓我們進入 屬性實踐,實踐內容如:將 標簽行高設置為20px。 代碼塊 結果圖 註意:使 ...
  • 設置字元和單詞間距介紹 屬性名 | 單位 |描述 | | letter spacing | px|設置字元間距 word spacing | px |設置單詞間距 letter spacing設置字元間距 屬性原理是:根據要設置的文本每一個字元之間的間距。 讓我們進入 屬性的實踐,實踐內容如:將 頁 ...
  • text indent屬性介紹 屬性值單位 | 描述 | em | 比如:1em 就代表縮進1個字,2em縮進2個字.....。 由於簡單我就不過多的介紹了直接上代碼了哦,註意: 屬性的值支持為負數具體園友可以嘗試下。 代碼塊 結果圖 ...
  • 幾個月前,我的任務是將我們組的 Vue.js 項目構建配置升級到 Webpack 4。我們的主要目標之一是利用 tree shaking 的優勢,即 Webpack 去掉了實際上並沒有使用的代碼來減少包的大小。現在,tree shaking 的好處將根據你的代碼庫而有所不同。由於我們的幾個架構決策, ...
  • text transform屬性介紹 屬性就是設置 頁面中的標簽裡面的文本大小寫, 屬性常用的屬性值有三種: 、`uppercase lowercase`,不常用的屬性值在這筆者就不進行一一說明瞭。 text transform屬性值說明表 屬性值 |描述 | none | 預設。定義帶有小寫字母和 ...
  • text decoration屬性介紹 屬性是用來設置文本修飾線呢, 屬性一共有4個值。 text decoration屬性值說明表 值|作用 | none |去掉文本修飾線 underline | 設置下劃線 overline|設置上劃線 line through|設置刪除線 HTML標簽自帶修飾 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...