简体   繁体   English

Javascript 中 unshift() 与 push() 的时间复杂度

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

I know what is the difference between unshift() and push() methods in JavaScript, but I'm wondering what is the difference in time complexity?我知道 JavaScript 中的unshift()push()方法有什么区别,但我想知道时间复杂度的区别是什么?

I suppose for push() method is O(1) because you're just adding an item to the end of array, but I'm not sure for unshift() method, because, I suppose you must "move" all the other existing elements forward and I suppose that is O(log n) or O(n)?我想push()方法是 O(1) 因为你只是在数组末尾添加一个项目,但我不确定unshift()方法,因为我想你必须“移动”所有其他现有元素向前,我想是 O(log n) 还是 O(n)?

push() is faster. 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());


Update更新

The above does not take into consideration the order of the arrays.以上没有考虑数组的顺序。 If you want to compare them properly, you must reverse the pushed array.如果要正确比较它们,则必须反转推送的数组。 However, push then reverse is still faster by ~ 10ms for me on chrome with this snippet:但是,使用以下代码段,在 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}`);

The JavaScript language spec does not mandate the time complexity of these functions, as far as I know.据我所知,JavaScript 语言规范并没有强制要求这些函数的时间复杂度。

It is certainly possible to implement an array-like data structure (O(1) random access) with O(1) push and unshift operations.使用 O(1) pushunshift操作实现类数组数据结构(O(1) 随机访问)当然是可能的。 The C++ std::deque is an example. C++ std::deque就是一个例子。 A Javascript implementation that used C++ deques to represent Javascript arrays internally would therefore have O(1) push and unshift operations.一个JavaScript实现,它使用C ++双端来表示内部的Javascript阵列将因此具有O(1) pushunshift操作。

But if you need to guarantee such time bounds, you will have to roll your own, like this:但是,如果您需要保证这样的时间范围,则必须自己滚动,如下所示:

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

For people curious about the v8 implementation here is the source .对于对 v8 实现感到好奇的人,这里是源代码 Because unshift takes an arbitrary number of arguments, the array will shift itself to accommodate all arguments.由于unshift接受任意数量的参数,数组将自行移位以容纳所有参数。

UnshiftImpl ends up calling AddArguments with a start_position of AT_START which kicks it to this else statement 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);

and takes it to the MoveElements .并将其带到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);
    }
  }

I also want to call out that v8 has a concept of different element kinds depending what the array contains.我还想指出 v8 有一个不同element kinds的概念,具体取决于数组包含的内容。 This also can affect the performance.这也会影响性能。

It's hard to actually say what the performance is because truthfully it depends on what types of elements are passed, how many holes are in the array, etc. If I dig through this more, maybe I can give a definitive answer but in general I assume since unshift needs to allocate more space in the array, in general you can kinda assume it's O(N) (will scale linerally depending on the number of elements) but someone please correct me if I'm wrong.实际上很难说性能是什么,因为说实话,它取决于传递的元素类型,数组中有多少个孔等等。如果我深入研究这个,也许我可以给出明确的答案,但总的来说我假设由于unshift需要在数组中分配更多空间,因此通常您可以假设它是 O(N)(将根据元素数量线性缩放),但如果我错了,请有人纠正我。

恕我直言,这取决于 javascript 引擎……如果它将使用链表,则 unshift 应该很便宜……

One way of implementing Arrays with both fast unshift and push is to simply put your data into the middle of your C-level array.使用快速 unshift 和 push 实现数组的一种方法是简单地将数据放入 C 级数组的中间。 That's how perl does it, IIRC.这就是 perl 的做法,IIRC。

Another way to do it is have two separate C-level arrays, so that push appends to one of them, and unshift appends to the other.另一种方法是使用两个单独的 C 级数组,以便将 push 附加到其中一个,并将 unshift 附加到另一个。 There's no real benefit to this approach over the previous one, that I know of.据我所知,与前一种方法相比,这种方法没有真正的好处。

Regardless of how it's implemented, a push or and unshift will take O(1) time when the internal C-level array has enough spare memory, otherwise, when reallocation must be done, at least O(N) time to copy the old data to the new block of memory.不管它是如何实现的,当内部 C 级数组有足够的空闲内存时,push 或 unshift 将花费 O(1) 时间,否则,当必须进行重新分配时,至少需要 O(N) 时间来复制旧数据到新的内存块。

Yes, you are right.是的,你是对的。 The default Complexity of push() is O(1) and unshift() is O(n). push()的默认复杂度为 O(1), unshift()为 O(n)。 Because unshift() has to increment all the elements that already present in the Array.因为unshift()必须增加 Array 中已经存在的所有元素。 But, push() has to insert an element at the end of the array, so none of the Array elements' index has to change.但是, push()必须在数组的末尾插入一个元素,因此所有 Array 元素的索引都不必更改。 But, push() can also be said with Complexity of O(n) because of dynamic allocation of memory.但是,由于内存的动态分配, push()也可以说复杂度为 O(n)。 In javascript, when you create a new Array without specifying the size you need, it will create an Array of the default value.在 javascript 中,当您创建一个新的 Array 而不指定您需要的大小时,它将创建一个默认值的 Array。 Until the default size gets filled, the push operation takes O(1) Complexity.在默认大小被填满之前,推送操作需要 O(1) 复杂度。 But, if the default size is full, the compiler has to create a new Contiguous block of memory which is twice the size of the default memory, and copy the already existing elements to the newly allocated memory.但是,如果默认大小已满,编译器必须创建一个新的连续内存块,其大小是默认内存的两倍,并将已存在的元素复制到新分配的内存中。 So, it takes O(n) time to move the elements from one Contiguous block of memory to another Contiguous block of memory.因此,将元素从一个连续内存块移动到另一个连续内存块需要 O(n) 时间。

If you know the number of elements that you are going to put in the array, you can avoid getting O(n) for inserting an element.如果您知道要放入数组的元素数量,则可以避免在插入元素时获得 O(n)。

  1. Initialize the Array with the required size, and fill it with a dummy value.用所需的大小初始化数组,并用一个虚拟值填充它。 let array = new Array(size).fill(0)
  2. Iterate through the elements that you wanna push and change the values by its index.遍历要推送的元素并按其索引更改值。
for (let i = 0; i < size; i++) {
  array[i] = i
}

So, instead of push() we altered the index of the elements in their position.因此,我们不是push()而是更改了元素位置的索引。 It's way more memory efficient and less complex than creating an array with a default value and pushing elements to it.与创建具有默认值的数组并将元素推送到该数组相比,它的内存效率更高且更简单。 As we are using up only the required amount of memory, no extra memory is wasted on it.由于我们只使用了所需的内存量,因此不会浪费额外的内存。

Short Answer简答

  • Time Complexity for push : O(n) push的时间复杂度:O(n)
  • Time Complexity for unshift : O(m + n) unshift的时间复杂度:O(m + n)

Where:在哪里:

  • m is the length of the existing array. m是现有数组的长度。
  • n is the number of elements to be added. n是要添加的元素的数量。
  • Time Complexity for pop : O(1) pop的时间复杂度:O(1)
  • Time Complexity for shift : O(n), where n is the length of the array shift的时间复杂度:O(n),其中n是数组的长度

Long Answer长答案

Here's a Array Data Structure implementation in JavaScript, hope this will make things clear.这是 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