重温:数据结构与算法 - 03数组

xwzz.jpg

重温:数据结构与算法 - 开篇
重温:数据结构与算法 - 复杂度分析(一)
重温:数据结构与算法 - 复杂度分析(二)

本章开始,学习第一个数据结构:数组

什么数组?

数组是一种 线性表 数据结构。它用一组 连续的内存空间,来存储一组 相同类型 的数据。

线性表

概念:数据排成一条线一样的数据结构。

除了数组,还有链表、队列、栈等都是线性表结构。

线性表数据结构.jpg

而与之对立的概念称为非线性表,属于这类数据结构的有:树、图、堆等。

非线性表数据结构.jpg

数组:连续的内存空间和相同类型

相同类型:很好理解,存储的数据都属于某一种类型。

连续的内存空间

数据内存图.jpg

上面是一个大小为10整型数组内存图,假设分配的内存地址从1000开始,那么第一个元素内存地址就是1000-1003的位置(整型数据占4个字节),总共有10个元素,最后的结束位置就是1039,这就是连续的内存。

数组:高效的查询

试想,已知数组a的首地址是1000,怎么最快的定位到a[1]内存地址?

a[1]内存地址 = 1000 + 4 = 1004

a[2]、a[3]呢?

a[2]内存地址 = 1000 + 2 * 4 = 1008

a[3]内存地址 = 1000 + 3 * 4 = 1012

发现规律,并得出寻址公式

a[i]内存地址 = base_address + i * data_type_size

有了这个公式便能做到快捷的读写数组元素,这种特性称之为随机访问

这里还有一个小知识,数组的下标从0开始是为了更方便的套入到寻址公式中计算,如果从1开始每次寻址时需要多1步减法操作,数组作为最常用的数据结构,多少会有点性能消耗。

有了寻址公式,通过下标查询某元素的时间复杂度为:O(1)

数组:低效删除和插入

  • 为什么会低效?
  • 有什么改进方法吗?

插入操作

假设数组的长度为n,我们想把某个数据插入到索引k位置,为了腾出索引k所在内存空间,就需要把k至数组末尾所有元素顺序往后移动一位。

试着分析下上面这段操作的时间复杂度:

如果要插入的元素索引为最后1个元素,需要移动元素为:0

插入下标为倒数第2元素,需要移动元素:1

插入下标为倒数第3元素,需要移动元素:2

...

插入小标为第1个元素,需要移动元素:n

插入元素到任意位置的概率是1/n

f(n) = 1+2+3+...+n / n = n/2*(n+1) / n = n(n+1) / 2n

T(n) = O( (n+1) / 2 ) = O(n)

通过以上分析,得出插入有序数组操作的时间复杂度如下:

  • 最好情况时间复杂度为:O(1)
  • 最坏情况时间复杂度为:O(n)
  • 平均情况时间复杂度为:O(n)

优化插入?

如果插入前提需保证数组的数据是有序的,那么就必须按照上面的方法来搬移k之后的元素。

但如果仅是将某个元素插入到指定位置,而不考虑元素中原有的顺序,我们可以先将原k元素放到数组末尾,再将新元素插入到k的位置,如图:

无序插入.jpg

利用上面这种方式,在特定场景中,它的时间复杂度为:O(1)

删除操作

与插入类似,要删除数组中k索引的元素,就需要把k至数组末尾的数据都向前移动1位,从而保证数组的内存连续性,那么删除末尾元素移动为0,删除第1个元素移动为n,所以删除操作的时间复杂度和插入是一样的:

  • 最好情况时间复杂度为:O(1)
  • 最坏情况时间复杂度为:O(n)
  • 平均情况时间复杂度为:O(n)

优化删除?

当然,在某些特定的场景下,我们也可以对删除操作进行优化。例如,我们有a,b,c,d,e,f,g,h组成的一个数组,依次删除a,b,c时,位移操作就要进行3n遍;如果现在的场景并不要求数组中实时的连续性,就可以分别将a、b、c先标记成删除状态,当数组没有多余的存储空间时,再做统一的实际删除。

标记删除.jpg

这样就大大减少了每次删除元素所需的位移操作。

这种标记删除状态的思想,也是JVM GC垃圾回收机制中所使用的到的思想。

ArrayList

谈到数组,使用Java语言就必离不开ArrayList,ArrayList是Java语言为我们提供的通过数组实现的数据存储容器,ArrayList的好处就在于它为开发者已经封装好了增、删、查等算法,并支持动态扩容

ArrayList的动态扩容

我们都知道数组在创建时,它的内存大小就固定了,例如下面这个arr数组的内存大小为3,当我们给第4个元素赋值运行时时就会抛出ArrayIndexOutOfBoundsException.

int[] arr = new int[3];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
arr[3] = 4; //运行时抛出异常

当你使用ArrayList时,就无此烦恼:

ArrayList list = new ArrayList(3);
list.add(1);
list.add(2);
list.add(3);
list.add(4); //可以正常添加

通过ArryList源码我们可以发现其原理,首先ArrayList为我们创建了一个大小为3的数组(以下代码为ArrayList源码):

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];     //初始化一个我们传入大小为3的数组
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

其次在添加元素时,如果数组大小不够用,扩容一个新的1.5倍的数组,然后再将原数组copy到新数组中,最后将元素加入:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);   // 这个方法会去调用扩容相关方法
    elementData[size++] = e;            // 最后加入元素
    return true;
}

// 扩容核心方法
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);     //扩容1.5倍
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);  //copy一个新的大小
}

使用数组,还是使用ArrayList?

乍一看,ArrayList相比数组要好用的多,我们无需关心数组的大小是否够用,也无需知道增删操作的算法如何实现。

  • 是的,大多数情况下使用ArrayList就能满足我们大多数需求;

  • 但当你要存储一组基本数据类型数据,且对性能要求较高时,可以优先考虑使用数组,ArrayList在存储基本数据类型数据时,需要进行装箱、拆箱操作,会有一定的性能损耗。

  • 当数据大小已知,无删、插、扩容需求时,也可考虑使用数组。

JVM GC标记清除算法

前文提到JVM GC标记清除算法这里简单介绍下。

基本思想:标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。

  • 在标记阶段,首先通过根节点做搜索,标记所有从根节点开始的可达对象,未被标记的对象就是未被引用的垃圾对象;
  • 再在清除阶段,清除所有未被标记的对象。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容