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


70

我知道JavaScriptunshift()push()方法之间的区别是什么,但是我想知道时间复杂度有什么区别?

我想push()方法是O(1),因为您只是将一个项目添加到数组的末尾,但是我不确定unshift()方法,因为我想您必须“将”所有其他现有元素“向前移动”,并且我想那是O(log n)或O(n)?


时间复杂度是什么意思?执行时间处理时间?
2012年

使用智能的稀疏数组实现,unshift可以接近恒定时间,但是我想知道使常规数组访问复杂化是否值得。我个人认为我从未写过电话给unshift
Pointy

15
@therao-他用big-O表示标准的计算机科学定义
Nemo 2012年

Answers:


24

据我所知,JavaScript语言规范并未规定这些功能的时间复杂性。

当然可以用O(1)pushunshift操作实现类似数组的数据结构(O(1)随机访问)。C ++std::deque就是一个例子。因此,使用C ++双端队列在内部表示Javascript数组的Javascript实现将具有O(1)pushunshift操作。

但是,如果您需要保证这样的时限,则必须自己动手,如下所示:

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


4
那么V8的复杂性是什么?
light24bulbs

64

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


更新资料

上面没有考虑数组的顺序。如果要正确比较它们,则必须反转推入的数组。但是,10ms使用此代码段,在chrome上,“推入后退”功能仍然快〜

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}`);


设置越大,则在我的机器上,使用上面@Shanti的代码,i <150000不变,我的机器macpro慢250倍以上;后面引用的jsperf示例使用仅包含4个元素的数组。
snowcode 2015年

1
@TheHe似乎是正确的,我的第一个测试是在Chrome上运行的(我在上面的评论),然后我在Safari的同一台计算机上运行了相同的测试,push(...)速度提高了10%。我没想到JavaScript引擎之间会有如此大的差异。!(我刚刚意识到这个q已有2年历史了,而Safari已经走了很长一段路,我使用的Safari 7.1.6是MacPro 2014模型。)
snowcode 2015年

与在Chrome 48 Win10上的推入/弹出速度相比,移/移速度要慢94%。
克里斯·莫斯奇尼

1
如果有人好奇,使用pushwithshift会比unshiftwith更快pop
douggard '18

在野生动物园中13.0unshift耗时8毫秒,push耗时
毫秒

9

对于那些对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)(将根据元素的数量线性缩放),但是如果我错了,请有人纠正我。


3

恕我直言,这取决于javascript-engine ...如果它将使用链表,则unshift应该非常便宜...


13
逆足大多数网站会去通过地板如果阵列是用链表实现...
史蒂芬路

1
对。但是对于使用链表进行的平移操作,您会得到O(1)复杂度。因此,这取决于用例。但是大多数站点宁愿优化推送不变。
哈里·莫雷诺

认为没有站点可以优化(在更改底层抽象数据类型时)Array构造吗?因此它完全取决于JS-VM的内部结构,优化和基础数据类型。
TheHe

3

同时实现快速平移和推入的数组的一种方法是将数据简单地放入C级数组的中间。这就是perl的工作方式,IIRC。

做到这一点的另一种方法是有两个单独的C级数组,以便将push追加到其中一个,将unshift追加到另一个。我知道,与前一种方法相比,这种方法没有真正的好处。

无论实现方式如何,当内部C级数组具有足够的备用内存时,推入和取消移位都将花费O(1)时间,否则,当必须进行重新分配时,至少需要O(N)时间来复制旧数据。到新的内存块。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.