初学数据结构--动态数组

动态数组

数组的局限性

目前为止所实现的数组类,有一个非常严重的局限性,就是这个数组实际使用的还是一个静态数组,内部容量有限。在实际使用的时候,我们往往无法预估要在这个数组中存入多少个元素

解决方案

在这种情况下,如果容量首次开太大,可能会浪费很多空间,但容量太小,又有可能不够用。这时候,需要有一种解决方案使得这个数组的容量是可伸缩的,也就是所谓的动态数组

思路

  1. 首先,原数组data,容量capacity为4,数组中元素size为4
  2. 然后新开一个数组new data(原数组data),开的空间要比原来大一些(从4-->8)
  3. 遍历原数组data,赋值到new data中。此时容量capacity为8,数组中的元素size为4
  4. 本身data指向4个空间的数组,现在指向8个空间的数组(new data也指向它)

图解操作:

  • 总结
    整个过程封装在一个函数内,对于new data这个变量在函数执行完便失效了,而data由于是类的成员变量,与整个类的生存周期一致(只要类还在使用,data就是有效的)
  • 细节
    对于原来4个空间的数组,由于没有对象指向它,所以利用java的垃圾回收机制将其回收

具体代码实现

// 将数组空间的容量变成newCapacity大小
public void resize(int newCapacity) {
    E[] newData = (E[]) new Object[newCapacity];
    for (int i = 0; i < size; i++) {
        newData[i] = data[i];
    }
    // 由newData指向data
    data = newData;
}

动态数组的优势

使用上述方法,让我们这个数组类拥有容量的动态伸缩的能力,所以在用这个数组类的时候,使用者不必关心数组容量是否够用的问题

简单的时间复杂度分析

前提

到目前为止,都主要以编程的思想来实现我们代码的逻辑,而对于这段代码的性能方面,我们一无所知。因此才需要使用复杂度分析的方式,来解析我们的代码

定义

  • 通常我们用O(1),O(n),O(logn),O(n^2)来描述一个算法的时间复杂度
  • O描述的是算法的运行时间和输入数据之间的关系

以代码做演示:

注:实际上,我们忽略了很多常数。比如for循环里,从nums数组中取数,还有sum相加的过程等等,所花的时间都是常量。

  • 实际时间T=c1*n+c2,c1表示n次操作每次所耗费的时间常数,c2表示完成算法内其他操作所耗费时间

分析动态数组的时间复杂度

添加操作
  • addLast(e) 向数组末尾添加一个元素,O(1)意味着,消耗时间跟数据的规模大小无关,无论数组中有多少个元素,都能在常数时间里完成
  • addFirst(e)** O(n)
  • add(index, e) 取决于index的位置,考虑极端情况则是演变成addLast(e)的O(1)操作,亦或者是退变成addFirst(e)的O(n)的操作,平均操作O(n/2)~O(n)
  • 在算法时间复杂度分析上,通常我们关注的是最坏最糟糕的情况。

综上所述,对于我们的动态数组来说,添加操作时间复杂度是(n)级别的。

修改操作
  • set(index, e) O(1)
  • 修改操作在动态数组中非常简单,只需要知道要修改的元素所对应的索引,直接利用set(index, e)。这个时间复杂度是O(1)级别的,这是数组最大的优势,专业术语是,支持随机访问。只要知道索引是谁,便可以一下子访问到它

总结:我们可以轻松的使用索引,去检索数组中的元素,那么在性能上便有非常强的优势。

防止复杂度震荡

复杂度震荡
  1. 假设现在我们有一个数组,容量是n,并且装满了元素。
  2. 这时候,我想添加一个元素,显然是需要进行扩容,容量变为2n,耗时O(n)的时间。
  3. 但是此时,我又删除了一个元素触发了缩容操作,耗时O(n)的时间。
  4. 当我们每次触发缩容或扩容操作,都会耗费O(n)额复杂度,那么这便是复杂度的震荡
分析

在特殊情况下,我们频繁的添加和删减操作,导致过于着急的去扩容或缩容
图解:

解决方案

可以采用一种相对懒惰的策略。

  • 比如说,一个满的数组,容量n,添加元素需要进行扩容,容量变为2n
  • 但在这时,在进行删除元素后,不立即进行缩容操作,而是再等等
  • 如果后面一直有删除操作的话,删除到整个数组容积的1/4,再触发缩容操作。缩容数组的1/2,而不是直接缩容到1/4
  • 此时,数组中存在1/4的元素,还预留了1/4的空间

通过这样的策略,防止了复杂度的震荡,从而有效的提升整体的性能

具体代码实现

// 从数组中删除index位置的元素,返回删除的元素
public E remove(int index) {
    if (index < 0 || index >= size) {
        throw new IllegalArgumentException("Remove failed. Index is illegal.");
    }
    E ret = data[index];
    for (int i = index + 1; i < size; i++) {
        data[i - 1] = data[i];
    }
    size--;
    // 端点(最后一位)要置空
    data[size] = null; // loitering objects != memory leak
    if (size == data.length / 4 && data.length / 2 != 0) {
        resize(data.length / 2);
    }
    return ret;
}

转载于:https://my.oschina.net/loubobooo/blog/1919647

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值