insert方法在Ruby Array中的复杂性顺序

时间:2015-10-13 09:43:18

标签: arrays ruby complexity-theory

在Ruby中,当n是数组大小时,在Array中插入的预期数量级是多少? 感谢

2 个答案:

答案 0 :(得分:5)

TL; DR追加(一种特殊形式的插入)项目到数组的末尾通常在O(1)时间内完成。

让我们来看看(MRI)ruby源代码,看看为什么会这样。我们从这行ruby代码开始:

a = [1,2]

Ruby准备一个数组对象,然后在this C function中初始化它。 检查参数的有效性,然后检查数组的sets the capacity of the new array to the estimated length

ary_resize_capa(ary, len);

数组的容量是数组在从操作系统分配的内存块中可能保存的元素数。 数组的长度(数组实际保存的元素数)始终小于或等于容量。 通过设置数组的容量,ruby确保分配足够的内存来保存数组中len个项目。

现在,让我们在数组的末尾添加一个元素:

a << 3

source of the << function看起来像这样:

VALUE
rb_ary_push(VALUE ary, VALUE item)
{
    long idx = RARRAY_LEN(ary);
    VALUE target_ary = ary_ensure_room_for_push(ary, 1);
    RARRAY_PTR_USE(ary, ptr, {
    RB_OBJ_WRITE(target_ary, &ptr[idx], item);
    });
    ARY_SET_LEN(ary, idx + 1);
    return ary;
}

这段代码看起来并不太可怕。它发现新元素的索引(idx)是数组的长度,确保数组有足够的内存来保存新元素(ary_ensure_room_for_push),将新元素写入数组,并增加数组长度。

当数组的容量大于其长度时,不需要在ary_ensure_room_for_push中分配更多内存,操作可以在O(1)时间内完成。

当数组的容量等于其长度时(数组中的内存量可以精确地保存它所拥有的元素数),ary_ensure_room_for_push需要增加容量,以便可以保留另外一个元素数组。 让我们看看这是如何完成的:

static VALUE
ary_ensure_room_for_push(VALUE ary, long add_len)
{
    long old_len = RARRAY_LEN(ary);
    long new_len = old_len + add_len;
    long capa;

    // ...

    rb_ary_modify(ary);
    capa = ARY_CAPA(ary);
    if (new_len > capa) {
        ary_double_capa(ary, new_len);
    }

    return ary;
}

如果请求的长度超过当前容量,我们会看到ary_ensure_room_for_push将数组容量加倍(引擎盖ary_double_capa使用我们在数组初始化期间看到的ary_resize_capa方法)。此代码从操作系统请求新的(更大的)内存块,并将所有数组元素复制到此新内存中。我们无法确切地说出复制操作有哪些复杂性(不要过多地考虑操作系统内部),但我们假设在最坏的情况下它是O(n)。

当新元素符合数组容量时,这会导致O(1)时间向数组添加元素;如果超出容量,则会导致O(n)。

仅供参考:将容量加倍(而不是将其精确地增加所请求的长度)是一种巧妙的技巧,可以优化多次向阵列添加元素的情况。有了这个技巧,我们大部分时间都有O(1)时间进行追加操作。仅对于每个log(n)个追加操作,需要增加容量,从而产生O(n)运行时。

答案 1 :(得分:0)

复杂度为O(N),因为此方法在Memmove()方法下使用rb_ary_splice()本身就是O(N),看一下源代码:

rb_ary_insert(int argc, VALUE *argv, VALUE ary)
{
    long pos;

    rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
    rb_ary_modify_check(ary);
    if (argc == 1) return ary;
    pos = NUM2LONG(argv[0]);
    if (pos == -1) {
    pos = RARRAY_LEN(ary);
    }
    if (pos < 0) {
    pos++;
    }
    rb_ary_splice(ary, pos, 0, rb_ary_new4(argc - 1, argv + 1));
    return ary;
}

rb_ary_splice(VALUE ary, long beg, long len, VALUE rpl)
{
    long rlen;
    long olen;

    if (len < 0) rb_raise(rb_eIndexError, "negative length (%ld)", len);
    olen = RARRAY_LEN(ary);
    if (beg < 0) {
    beg += olen;
    if (beg < 0) {
        rb_raise(rb_eIndexError, "index %ld too small for array; minimum: %ld",
             beg - olen, -olen);
    }
    }
    if (olen < len || olen < beg + len) {
    len = olen - beg;
    }

    if (rpl == Qundef) {
    rlen = 0;
    }
    else {
    rpl = rb_ary_to_ary(rpl);
    rlen = RARRAY_LEN(rpl);
    olen = RARRAY_LEN(ary); /* ary may be resized in rpl.to_ary too */
    }
    if (beg >= olen) {
    VALUE target_ary;
    if (beg > ARY_MAX_SIZE - rlen) {
        rb_raise(rb_eIndexError, "index %ld too big", beg);
    }
    target_ary = ary_ensure_room_for_push(ary, rlen-len); /* len is 0 or negative */
    len = beg + rlen;
    ary_mem_clear(ary, olen, beg - olen);
    if (rlen > 0) {
        ary_memcpy0(ary, beg, rlen, RARRAY_CONST_PTR(rpl), target_ary);
    }
    ARY_SET_LEN(ary, len);
    }
    else {
    long alen;

    if (olen - len > ARY_MAX_SIZE - rlen) {
        rb_raise(rb_eIndexError, "index %ld too big", olen + rlen - len);
    }
    rb_ary_modify(ary);
    alen = olen + rlen - len;
    if (alen >= ARY_CAPA(ary)) {
        ary_double_capa(ary, alen);
    }

    if (len != rlen) {
        RARRAY_PTR_USE(ary, ptr,
               MEMMOVE(ptr + beg + rlen, ptr + beg + len,
                   VALUE, olen - (beg + len)));
        ARY_SET_LEN(ary, alen);
    }
    if (rlen > 0) {
        MEMMOVE(RARRAY_PTR(ary) + beg, RARRAY_CONST_PTR(rpl), VALUE, rlen);
    }
    }
    RB_GC_GUARD(rpl);
}

这里是对MEMMOVE功能的引用:

https://stuff.mit.edu/afs/sipb/contrib/linux/arch/microblaze/lib/memmove.c