簡體   English   中英

Javascript 中 unshift() 與 push() 的時間復雜度

[英]Time complexity of unshift() vs. push() in Javascript

我知道 JavaScript 中的unshift()push()方法有什么區別,但我想知道時間復雜度的區別是什么?

我想push()方法是 O(1) 因為你只是在數組末尾添加一個項目,但我不確定unshift()方法,因為我想你必須“移動”所有其他現有元素向前,我想是 O(log n) 還是 O(n)?

push() 更快。

js>function foo() {a=[]; start = new Date; for (var i=0;i<100000;i++) a.unshift(1); return((new Date)-start)}
js>foo()
2190
js>function bar() {a=[]; start = new Date; for (var i=0;i<100000;i++) a.push(1); return((new Date)-start)}
js>bar()
10

 function foo() {a=[]; start = new Date; for (var i=0;i<100000;i++) a.unshift(1); return((new Date)-start)} console.log(foo()) function bar() {a=[]; start = new Date; for (var i=0;i<100000;i++) a.push(1); return((new Date)-start)} console.log(bar());


更新

以上沒有考慮數組的順序。 如果要正確比較它們,則必須反轉推送的數組。 但是,使用以下代碼段,在 chrome 上推動然后反向仍然快約10ms

 var a=[]; var start = new Date; for (var i=0;i<100000;i++) { a.unshift(1); } var end = (new Date)-start; console.log(`Unshift time: ${end}`); var a=[]; var start = new Date; for (var i=0;i<100000;i++) { a.push(1); } a.reverse(); var end = (new Date)-start; console.log(`Push and reverse time: ${end}`);

據我所知,JavaScript 語言規范並沒有強制要求這些函數的時間復雜度。

使用 O(1) pushunshift操作實現類數組數據結構(O(1) 隨機訪問)當然是可能的。 C++ std::deque就是一個例子。 一個JavaScript實現,它使用C ++雙端來表示內部的Javascript陣列將因此具有O(1) pushunshift操作。

但是,如果您需要保證這樣的時間范圍,則必須自己滾動,如下所示:

http://code.stephenmorley.org/javascript/queues/

對於對 v8 實現感到好奇的人,這里是源代碼 由於unshift接受任意數量的參數,數組將自行移位以容納所有參數。

UnshiftImpl結束調用AddArgumentsstart_positionAT_START它踢這個else說法

  // If the backing store has enough capacity and we add elements to the
  // start we have to shift the existing objects.
  Isolate* isolate = receiver->GetIsolate();
  Subclass::MoveElements(isolate, receiver, backing_store, add_size, 0,
                         length, 0, 0);

並將其帶到MoveElements

  static void MoveElements(Isolate* isolate, Handle<JSArray> receiver,
                           Handle<FixedArrayBase> backing_store, int dst_index,
                           int src_index, int len, int hole_start,
                           int hole_end) {
    Heap* heap = isolate->heap();
    Handle<BackingStore> dst_elms = Handle<BackingStore>::cast(backing_store);
    if (len > JSArray::kMaxCopyElements && dst_index == 0 &&
        heap->CanMoveObjectStart(*dst_elms)) {
      // Update all the copies of this backing_store handle.
      *dst_elms.location() =
          BackingStore::cast(heap->LeftTrimFixedArray(*dst_elms, src_index))
              ->ptr();
      receiver->set_elements(*dst_elms);
      // Adjust the hole offset as the array has been shrunk.
      hole_end -= src_index;
      DCHECK_LE(hole_start, backing_store->length());
      DCHECK_LE(hole_end, backing_store->length());
    } else if (len != 0) {
      WriteBarrierMode mode = GetWriteBarrierMode(KindTraits::Kind);
      dst_elms->MoveElements(heap, dst_index, src_index, len, mode);
    }
    if (hole_start != hole_end) {
      dst_elms->FillWithHoles(hole_start, hole_end);
    }
  }

我還想指出 v8 有一個不同element kinds的概念,具體取決於數組包含的內容。 這也會影響性能。

實際上很難說性能是什么,因為說實話,它取決於傳遞的元素類型,數組中有多少個孔等等。如果我深入研究這個,也許我可以給出明確的答案,但總的來說我假設由於unshift需要在數組中分配更多空間,因此通常您可以假設它是 O(N)(將根據元素數量線性縮放),但如果我錯了,請有人糾正我。

恕我直言,這取決於 javascript 引擎……如果它將使用鏈表,則 unshift 應該很便宜……

使用快速 unshift 和 push 實現數組的一種方法是簡單地將數據放入 C 級數組的中間。 這就是 perl 的做法,IIRC。

另一種方法是使用兩個單獨的 C 級數組,以便將 push 附加到其中一個,並將 unshift 附加到另一個。 據我所知,與前一種方法相比,這種方法沒有真正的好處。

不管它是如何實現的,當內部 C 級數組有足夠的空閑內存時,push 或 unshift 將花費 O(1) 時間,否則,當必須進行重新分配時,至少需要 O(N) 時間來復制舊數據到新的內存塊。

是的,你是對的。 push()的默認復雜度為 O(1), unshift()為 O(n)。 因為unshift()必須增加 Array 中已經存在的所有元素。 但是, push()必須在數組的末尾插入一個元素,因此所有 Array 元素的索引都不必更改。 但是,由於內存的動態分配, push()也可以說復雜度為 O(n)。 在 javascript 中,當您創建一個新的 Array 而不指定您需要的大小時,它將創建一個默認值的 Array。 在默認大小被填滿之前,推送操作需要 O(1) 復雜度。 但是,如果默認大小已滿,編譯器必須創建一個新的連續內存塊,其大小是默認內存的兩倍,並將已存在的元素復制到新分配的內存中。 因此,將元素從一個連續內存塊移動到另一個連續內存塊需要 O(n) 時間。

如果您知道要放入數組的元素數量,則可以避免在插入元素時獲得 O(n)。

  1. 用所需的大小初始化數組,並用一個虛擬值填充它。 let array = new Array(size).fill(0)
  2. 遍歷要推送的元素並按其索引更改值。
for (let i = 0; i < size; i++) {
  array[i] = i
}

因此,我們不是push()而是更改了元素位置的索引。 與創建具有默認值的數組並將元素推送到該數組相比,它的內存效率更高且更簡單。 由於我們只使用了所需的內存量,因此不會浪費額外的內存。

簡答

  • push的時間復雜度:O(n)
  • unshift的時間復雜度:O(m + n)

在哪里:

  • m是現有數組的長度。
  • n是要添加的元素的數量。
  • pop的時間復雜度:O(1)
  • shift的時間復雜度:O(n),其中n是數組的長度

長答案

這是 JavaScript 中的數組數據結構實現,希望這能讓事情變得清晰。

class XArray {
  constructor() {
    Object.defineProperties(this, {
      length: {
        writable: true,
        enumerable: false,
        configurable: false,
        value: 0,
      },
    });

    /** Configure the output of the Array object to return only values */
    const runtimeConsole = console;
    console = {
      ...console,
      log: function (data) {
        if (XArray.isArray(data)) {
          runtimeConsole.log(Object.values(data));
        } else runtimeConsole.log(data);
      },
    };
  }

  /**
   * Adds element(s) to the end of the array
   *
   * Time Complexity: O(n)
   * @param  {...any} elements
   */
  push(...elements) {
    for (const element of elements) {
      this[this.length] = element;
      this.length++;
    }
    return this.length;
  }

  pop() {
    const element = this[this.length - 1];
    delete this[this.length - 1];
    this.length--;
    return element;
  }

  /**
   * Adds elements to the beginning of the array
   *
   * Time Complexity: O(m + n)
   *
   * @param  {...any} elements
   */
  unshift(...elements) {
    for (let i = this.length - 1; i >= 0; i--) {
      this[i + elements.length] = this[i];
    }
    for (const index in elements) {
      this[index] = elements[index];
      this.length++;
    }
    this.length;
  }

  shift() {
    const element = this[0];
    this.length--;

    for (let i = 0; i < this.length; i++) {
      this[i] = this[i + 1];
    }
    delete this[this.length - 1];
    return element;
  }

  static isArray(array) {
    return array instanceof XArray;
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM