重讀《學習JavaScript數據結構與演算法-第三版》- 第6章 鏈表(一)

来源:https://www.cnblogs.com/justbecoder/archive/2019/08/25/11410022.html
-Advertisement-
Play Games

定場詩 前言 本章為重讀《學習JavaScript數據結構與演算法》的系列文章,該章節主要講述數據結構 鏈表,以及實現鏈表的過程和原理。 鏈表 鏈表 ,為什麼要有這種數據結構呢?當然,事出必有因! 數組 最常用、最方便的數據結構, But ,當我們從數組的起點或中間插入或移動項的成本很高,因為我們需要 ...


定場詩

傷情最是晚涼天,憔悴廝人不堪言;
邀酒摧腸三杯醉.尋香驚夢五更寒。
釵頭鳳斜卿有淚,荼蘼花了我無緣;
小樓寂寞新雨月.也難如鉤也難圓。

前言

本章為重讀《學習JavaScript數據結構與演算法》的系列文章,該章節主要講述數據結構-鏈表,以及實現鏈表的過程和原理。

鏈表

鏈表,為什麼要有這種數據結構呢?當然,事出必有因!

數組-最常用、最方便的數據結構,But,當我們從數組的起點或中間插入或移動項的成本很高,因為我們需要移動數組元素。

鏈表,是存儲有序的元素集合。鏈表中的元素在記憶體中並不是連續放置的,每個元素由一個存儲自身的元素節點和一個指向下一個元素的引用(指針或鏈接)組成。

這個是重點,註意,圈起來,面試肯定考!

實現鏈表

我們要實現鏈表的結構以及對應的方法,大致為:元素插入、移除、獲取鏈表長度、查詢元素、是否為空等等...更詳細可看代碼實現邏輯。

數據結構越來越複雜嘍...註意領會精神...

/**
 * Node 節點類,用於生成自身節點與下一個元素的引用
 */
class Node {
  constructor(element) {
    this.element = element
    this.next = undefined
  }
}

/**
 * defaultEquals() 用於比較兩個非對象類的值是否相等
 */
function defaultEquals (a, b) {
  return a === b
}

/**
 * LinkedList 鏈表
 * 特點:鏈表存儲有序的元素集合,但元素在記憶體中並不連續存放。每個元素有一個存儲元素本身的節點和一個指向下一個元素的引用
 */
class LinkedList {
  constructor (equalsFn = defaultEquals) {
    // 鏈表長度
    this.count = 0
    // 第一個元素引用
    this.head = undefined
    // 用於比較元素是否相等,預設為defaultEquals,允許傳入自定義的函數來比較兩個對象是否相等。
    this.equalsFn = equalsFn
  }
}

註意:equalsFn參數,在比較對象類元素時,需要傳遞自定義方法來比較兩個對象的的值是否相等。

push()

向鏈表的尾部添加元素

/**
 * push() 添加元素到鏈表
 * @param {*} element 待添加的元素
 */
push (element) {
  let node = new Node(element)
  let current
  // 判斷鏈表是否為空
  if (this.head == null) {
    this.head = node
  } else {
    // 查詢最後的元素 - 特點 current.next == null
    current = this.head
    while (current.next != null) {
      current = current.next
    }
    // 將最後元素的下一個元素引用指向node
    current.next = node
  }
  this.count++
} 

getElementAt()

獲取指定索引位置的元素

/**
 * getElementAt() 返回索引位置元素
 * @param {Number} index 索引位置
 * @returns {*} node
 */
getElementAt (index) {
  // 判斷索引是否有效
  if (index < 0 || index > this.count) {
    return undefined
  }
  let node = this.head
  for (let i = 0; i < index && node != null; i++) {
    node = node.next
  }
  return node
}

insert()

在任意索引位置插入元素

/**
 * insert() 在任意位置插入元素
 * @param {*} element 待插入的元素
 * @param {Number} index 指定的索引位置
 * @returns {Boolean}
 */
insert (element, index) {
  // 判斷是否為合法index
  if (index < 0 || index > this.count) {
    return false
  }
  // 實例化節點
  let node = new Node(element)
  // 插入-第一個位置
  if (index === 0) {
    let current = this.head
    node.next = current
    this.head = node
  } else {
    // 查找到指定索引位置的前一個元素
    let previous = this.getElementAt(index - 1)
    let current = previous.next

    // 將上一個元素的next引用指向當前新添加的node,將node的next引用指向current,則完成插入
    previous.next = node
    node.next = current
  }
  // 鏈表長度+1
  this.count++
  return true
}

removeAt()

移除指定索引位置的元素

實現邏輯:判斷是否為鏈表第一項,若為第一項,則將this.head指向第二個元素即可;如果不是第一項,則獲取索引index位置的上一位置索引元素previous,以及下一位置索引元素current.next,將previous.next指向current.next即可

/**
 * removeAt() 移除指定位置的元素
 * @param {Number} index
 * @returns {*} element 移除的元素
 */
removeAt (index) {
  // 判斷是否是合法索引
  if (index < 0 || index > this.count) {
    return undefined
  }

  let current = this.head

  // 考慮是否是鏈表第一項
  if (index === 0) {
    this.head = current.next
  } else {
    // 方法一、 使用for迴圈迭代獲取指定索引位置的元素
    // 用於獲取上一位置元素
    // let previous
    // for (let i = 0; i < index; i++) {
    //   previous = current
    //   current = current.next
    // }
    // // 跳過當前元素,將上一個元素的next指向下一個元素
    // previous.next = current.next

    // 方法二、藉助getElementAt()方法
    // 查詢該索引位置的上一個元素
    let previous = this.getElementAt(index - 1)
    // 跳過中間元素,將上一個元素的next指向下一個元素
    current = previous.next
    previous.next = current.next
  }
  this.count--
  return current.element
}

indexOf()

查詢元素的索引位置

實現邏輯:迭代元素,比較每一個元素與目標元素是否相等;特別需要註意的是,對象類的值請傳入自定義的方法進行比較是否相等s

/**
 * indexOf() 查詢元素的索引位置
 * @param {*} element 待查詢的元素
 * @returns {Number} index 索引位置,不存在則返回-1
 */
indexOf (element) {
  let current = this.head
  for (let i = 0; i < this.count && current != null; i++) {
    // 判斷兩個元素是否相同
    if (this.equalsFn(element, current.element)) {
      return i
    }
    current = current.next
  }
  return - 1
}

remove()

移除指定的元素

/**
 * remove() 移除元素
 * @param {*} element 待移除的元素
 * @returns {*} element 移除的元素
 */
remove (element) {
  // 獲取索引位置
  let index = this.indexOf(element)
  // 移除指定索引位置的元素
  return this.removeAt(index)
}

size()

獲取鏈表長度

/**
 * size() 返回鏈表長度
 * @returns {Number} 鏈表長度
 */
size () {
  return this.count
}

isEmpty()

判斷鏈表是否為空

/**
 * isEmpty() 判斷鏈表是否為空
 * @returns {Boolean}
 */
isEmpty () {
  return this.count === 0
}

getHead()

獲取鏈表首位元素

/**
 * getHead() 獲取鏈表首位元素
 * @returns {*}
 */
getHead () {
  if (this.head != null) {
    return this.head.element
  }
  return undefined
}

clear()

清空鏈表

/**
 * clear() 清空鏈表
 */
clear () {
  this.count = 0
  this.head = undefined
}

toString()

鏈表字元串處理

/**
 * toString() 鏈表字元串化展示
 * @returns {String}
 */
toString () {
  // 判斷是否為空
  if (this.isEmpty()) {
    return ''
  }
  let objStr = `${this.head.element}`
  let current = this.head.next
  for (let i = 1; i < this.count; i++) {
    objStr = `${objStr},${current.element}`
    current = current.next
  }
  return objStr
}

使用鏈表

let linkedList = new LinkedList()

console.log(linkedList.isEmpty()) // true

// push 尾部壓入元素
linkedList.push(15)
linkedList.push(20)
/*
  LinkedList {
    count: 2,
    head: Node {
      element: 15,
      next: Node {
        element: 20,
        next: undefined
      }
    },
    equalsFn: [Function: defaultEquals]
  }
*/
console.log(linkedList)

console.log(linkedList.isEmpty()) // false

// getElementAt() 指定索引位置的元素
let node = linkedList.getElementAt(1)
console.log(node)

// insert() 任意索引位置插入元素
linkedList.insert(22, 1)
linkedList.insert(33, 1)
console.log(linkedList.toString()) // 15,33,22,20

// removeAt() 移除指定索引位置的元素
console.log(linkedList.removeAt(1)) // 33

// remove() 移除元素
console.log(linkedList.remove(15)) // 15

console.log(linkedList.toString()) // 22,20

console.log(linkedList.getHead()) // 22

console.log(linkedList.size()) // 2
console.log(linkedList.toString()) // 22,20

console.log(linkedList.isEmpty()) // false

linkedList.clear()
console.log(linkedList.isEmpty()) // true

再次強調下:如果鏈表中存儲的是對象類型值,請務必傳入指定的比較函數equalsFn自行進行比較,You should know why!

後記

以上就是胡哥今天給大家分享的內容,喜歡的小伙伴記得收藏轉發、點擊右下角按鈕在看,推薦給更多小伙伴呦,歡迎多多留言交流...

胡哥有話說,一個有技術,有情懷的胡哥!京東開放平臺首席前端攻城獅。與你一起聊聊大前端,分享前端系統架構,框架實現原理,最新最高效的技術實踐!

長按掃碼關註,更帥更漂亮呦!關註胡哥有話說公眾號,可與胡哥繼續深入交流呦!

胡哥有話說


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

-Advertisement-
Play Games
更多相關文章
  • 一、簡述 截至到目前 (2019.04),HBase 有兩個主要的版本,分別是 1.x 和 2.x ,兩個版本的 Java API 有所不同,1.x 中某些方法在 2.x 中被標識為 過時。所以下麵關於 API 的樣例,我會分別給出 1.x 和 2.x 兩個版本。完整的代碼見本倉庫: + "Java ...
  • 安裝 第一步:打開網址,https://www.mysql.com,點擊downloads之後跳轉到https://www.mysql.com/downloads/選擇Community選項 第二步: 第一步結束後程式會跳轉到https://dev.mysql.com/downloads/網址,點擊 ...
  • AOP: 面向切麵編程,偏向於處理業務的某個階段 適用場景: 1. 參數校驗:網路請求前的參數校驗,返回數據的格式校驗等等 2. 無痕埋點:統一處理埋點,降低代碼耦合度 3. 頁面統計:幫助統計頁面訪問量 4. 事務處理:攔截指定事件,添加觸發事件 5. 異常處理:發生異常時使用面向切麵的方式進行處 ...
  • ConstraintLayout 不能正常顯示,因為其中一個控制項的約束屬性寫錯了 ...
  • jQuery返回的對象本質上是一個JavaScript對象,而入口模塊則可以保存對應的節點的引用,然後供其它模塊操作 我們創建jQuery對象時可以給jQuery傳遞各種不同的選擇器,如下: false ;返回一個空jQuery對象 DOM節點 ;返回包含該DOM元素引用的jQuery對象。 bod ...
  • [TOC] Vue組件化開發 組件化開發: 1、組件化開髮指的是將複雜的業務拆分為一個有一個的組件 2、組件化開發的組件一般來說要靈活 3、組件化開發涉及到了Vue的js組件封裝,需要掌握Vue基礎、Vue實例方法與屬性、Vue.extend、Vue插件等知識 Vue實例方法與屬性 vm.$moun ...
  • 一、背景 1.如何設置標簽的背景顏色 (1)在CSS中有一個background-color:屬性值;,就是專門用來設置標簽的背景顏色。 (2)取值:具體單詞、RGB、RGBA、十六進位 例子: 2.如何設置背景圖片? (1)在CSS中有一個叫做background-image:url();的屬性, ...
  • TypeScript宗旨 我覺得Typescript的宗旨是 任何一個 TypeScript 程式,在手動刪去類型部分,將尾碼改成 .js 後,都應能夠正常運行。Typescript是javascript的超集,是編譯期行為,不引入額外開銷,不改變運行時行為,始終與 ESMAScript 語言標準一 ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...