线性表之动态数组

1、什么是数据结构

数据结构是计算机存储、组织数据的方式,数据结构分为线性结构、树形结构、图形结构。

  • 线性表就是线性结构的,而数组、链表、栈、队列、都属于线性表,由于哈希表中用到了数组,所以哈希表也算是线性结构的
  • 二叉树、AVL树、红黑树、B树、堆、Trie、哈夫曼树、并查集都属于树形结构。
  • 邻接矩阵、邻接表属于图形结构。
    本文主要讲下线性结构的数组。

2、数组

数组是一种顺序存储的线性表,所有内存地址都是连续的。
数组的定义如下:

int[] array=new int[] {11,22,33}

在内存中表示如下

image.png

上面数组中的元素是int型的,每个元素占4个字节,上面数组中有三个元素,所以在内存中申请了12个字节的内存空间,并且内存地址是连续的。
而且需要注意的是:数组一旦初始化后,容量就已经确定了,无法动态的修改其容量,但是在实际开发中我们想要的数组是可以修改容量,下面就来看下如何实现动态数组。

3、动态数组

设计动态数组前,先来看下向外提供哪些方法。

  • int size(); //元素的数量
  • boolean isEmpty();//数组中元素是否为空
  • boolean contains(E element);//数组中是否包含某个元素
  • void add(E element);//添加元素到最后面
  • void add(int index,E element);//向index位置添加元素
  • E get(int index);//获取index位置的元素
  • E set(int index,E element);//设置index位置的元素
  • E remove(int index);//删除某个位置的元素
  • E remove(E element);//删除某个元素
  • int indexOf(E element);//获取某个元素的index
  • clear();//清空元素
public class ArrayList<E> {

    /**
     * @return 返回数组中元素的个数
     */
    public int size() {
        return 0;
    }

    /**
     * @return 数组是否为空
     */
    public boolean isEmpty() {
        return false;
    }

    /**
     * @param element
     * @return 是否包含元素element
     */
    public boolean contains(E element) {
        return false;
    }

    /**
     * 添加元素
     * 
     * @param element
     */
    public void add(E element) {

    }

    /**
     * 向指定位置添加元素
     * 
     * @param index
     * @param element
     */
    public void add(int index, E element) {

    }

    /**
     * 获取指定位置的元素
     * 
     * @param index
     * @return
     */
    public E get(int index) {
        return null;
    }

    /**
     * 设置index位置的元素
     * 
     * @param index
     * @param element
     */
    public void set(int index, E element) {

    }

    /**
     * 删除指定位置的元素
     * 
     * @param index
     * @return
     */
    public E remove(int index) {
        return null;
    }

    /**
     * 删除元素
     * 
     * @param element
     * @return
     */
    public E remove(E element) {
        return null;
    }

    /**
     * 返回指定元素的位置
     * @param element
     * @return
     */
    public int indexOf(E element) {
        return -1;
    }
    
    /**
     * 清空元素
     */
    public void clear() {
        
    }
}

3.1、元素的添加

  • 如果元素添加在数组尾部,则直接添加;
  • 如果元素添加不在数组的尾部,则需要将数组的所有元素都向后移动一位,如果在头部添加则需要将数组中所有元素向后移动一位。


    image.png

    具体代码如下:

/**
 * 添加元素
 * 
 * @param element
 */
public void add(E element) {
//      elements[size] = element;
//      size++;
    add(size, element);
}

/**
 * 向指定位置添加元素
 * 
 * @param index
 * @param element
 */
public void add(int index, E element) {
    checkIndexForAdd(index);
    for (int i = size - 1; i >= index; i--) {
        elements[i + 1] = elements[i];
    }
    elements[index] = element;
    size++;
}

private void checkIndexForAdd(int index) {
    if (index < 0 || index > size)
        throw new IndexOutOfBoundsException("index is " + index + " size is " + size);
}

3.2、删除元素

删除元素不同于添加,我们需要将数组中的元素向前移动。而且需要对最后一个元素进行处理。

image.png

具体代码如下:

/**
 * 删除指定位置的元素
 * 
 * @param index
 * @return
 */
public E remove(int index) {
    checkIndex(index);
    E oldE = elements[index];
    for (int i = index + 1; i < size; i++) {
        elements[i - 1] = elements[i];
    }
    //如果数组中需要存入的数据类型为对象类型的,那么数组中存入的是对象的内存地址
    //在删除元素时需要将元素向前移动,如果不将数组的最后一个元素置成null,那么它在
    //下次被赋值之前都会一直引用这个对象,无法被垃圾回收。
    elements[size - 1] = null;
    size--;
    return oldE;
}

这里需要注意的时,删除时的index的check不同于add(),在add()时,允许向index=size的位置添加元素,而删除时,并不能删除index=size的位置添加元素,很好理解,因为这个位置并没有元素。具体的代码如下:

private void checkIndex(int index) {
    if (index < 0 || index >= size)
        throw new IndexOutOfBoundsException("index is " + index + " size is " + size);
}

删除元素还有一个方法未实现

/**
 * 删除元素
 * 
 * @param element
 * @return
 */
public E remove(E element) {
    return null;
}

这个方法需要用到indexOf()方法,这里先来实现一下

/**
 * 返回指定元素的位置
 * 
 * @param element
 * @return 返回-1,表示未找到元素
 */
public int indexOf(E element) {
    for (int i = 0; i < size; i++) {
        if (element == null) {
            if (elements[i] == null)
                return i;
        } else {
            if (element.equals(elements[i]))
                return i;
        }
    }
    return -1;
}

需要注意当查找的元素为null时,需要进行判断,否则会造成空指针异常。
有了indexOf()方法,我们就能通过该方法查找要删除元素的index,然后通过remove(int index)来删除元素了

/**
 * 删除元素
 * 
 * @param element
 * @return
 */
public E remove(E element) {
    return remove(indexOf(element));
}

3.3、其他方法的实现

/**
 * @return 返回数组中元素的个数
 */
public int size() {
    return size;
}

/**
 * @return 数组是否为空
 */
public boolean isEmpty() {
    return size==0;
}

/**
 * @param element
 * @return 是否包含元素element
 */
public boolean contains(E element) {
    return indexOf(element)!=-1;
}

/**
 * 获取指定位置的元素
 * 
 * @param index
 * @return
 */
public E get(int index) {
    checkIndex(index);
    return elements[index];
}

/**
 * 设置index位置的元素
 * 
 * @param index
 * @param element
 */
public void set(int index, E element) {
    checkIndex(index);
    elements[index]=element;
}

3.4、clear()方法的实现

3.4.1、当动态数组中存入的是基本类型的数据时,数组在内存中的表示如下

以int型数组举例:

int[] array=new int[]{11,22,33,44,55,66,77};

image.png

通过new关键字创建长度为7的数组,会存放在堆空间中,变量array会存在栈空间中,并将数组在堆中的内存首地址赋值给变量array。
数组中存入的数据为基本类型数据时,我们在清空元素时直接将size=0即可实现元素清空的效果。

3.4.2、数组中存入对象数据

Object[] objects=new Object[7];

内存分配如下:


image.png

此时数组中存入的并不是对象本身,而是对象的内存地址,在执行清空元素时,需要将数组中的数据置成null,否则只把size置成0,在数组中元素被覆盖前对象会一直被引用,而不能被垃圾回收器回收。

注意:有的人可能会将objects=null,但是这样我们创建的数组就会被垃圾回收,下次使用的时候还要重新创建数组,对象的创建和销毁也是很耗资源的,而直接将size设置为0,虽然数组中存入之前的数据但是不能被访问的,在再次向其中添加数据时,会直接覆盖旧数据。
完整的代码如下

package com.ygj;

public class ArrayList<E> {
    private int size;
    private E[] elements;
    private static final int DEFAULT_CAPACTITY = 10;

    public ArrayList() {
        this(DEFAULT_CAPACTITY);
    }

    public ArrayList(int capacity) {
        if (capacity <= DEFAULT_CAPACTITY)
            capacity = DEFAULT_CAPACTITY;
        elements = (E[]) new Object[capacity];
    }

    /**
     * @return 返回数组中元素的个数
     */
    public int size() {
        return size;
    }

    /**
     * @return 数组是否为空
     */
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * @param element
     * @return 是否包含元素element
     */
    public boolean contains(E element) {
        return indexOf(element) != -1;
    }

    /**
     * 添加元素
     * 
     * @param element
     */
    public void add(E element) {
//      elements[size] = element;
//      size++;
        add(size, element);
    }

    /**
     * 向指定位置添加元素
     * 
     * @param index
     * @param element
     */
    public void add(int index, E element) {
        checkIndexForAdd(index);
        ensureCapacity(size);
        for (int i = size - 1; i >= index; i--) {
            elements[i + 1] = elements[i];
        }
        elements[index] = element;
        size++;
    }

    /**
     * 扩容
     * 
     * @param capactity
     */
    private void ensureCapacity(int capactity) {
        if (capactity >= elements.length) {
            int newCapacity = capactity + (capactity >> 1);
            System.out.println("扩容 oldCapactity:" + elements.length + " newCapacity:" + newCapacity);
            E[] newElements = (E[]) new Object[newCapacity];
            for (int i = 0; i < size; i++) {
                newElements[i] = elements[i];
            }
            elements = newElements;
        }
    }

    private void checkIndexForAdd(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("index is " + index + " size is " + size);
    }

    private void checkIndex(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("index is " + index + " size is " + size);
    }

    /**
     * 获取指定位置的元素
     * 
     * @param index
     * @return
     */
    public E get(int index) {
        checkIndex(index);
        return elements[index];
    }

    /**
     * 设置index位置的元素
     * 
     * @param index
     * @param element
     */
    public void set(int index, E element) {
        checkIndex(index);
        elements[index] = element;
    }

    /**
     * 删除指定位置的元素
     * 
     * @param index
     * @return
     */
    public E remove(int index) {
        checkIndex(index);
        E oldE = elements[index];
        for (int i = index + 1; i < size; i++) {
            elements[i - 1] = elements[i];
        }
        // 如果数组中需要存入的数据类型为对象类型的,那么数组中存入的是对象的内存地址
        // 在删除元素时需要将元素向前移动,如果不将数组的最后一个元素置成null,那么它在
        // 下次被赋值之前都会一直引用这个对象,无法被垃圾回收。
        elements[size - 1] = null;
        size--;
        return oldE;
    }

    /**
     * 删除元素
     * 
     * @param element
     * @return
     */
    public E remove(E element) {
        return remove(indexOf(element));
    }

    /**
     * 返回指定元素的位置
     * 
     * @param element
     * @return 返回-1,表示未找到元素
     */
    public int indexOf(E element) {
        for (int i = 0; i < size; i++) {
            if (element == null) {
                if (elements[i] == null)
                    return i;
            } else {
                if (element.equals(elements[i]))
                    return i;
            }
        }
        return -1;
    }

    /**
     * 清空元素
     */
    public void clear() {
        for (E e : elements)
            e = null;
        size = 0;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("size=" + size + " capactity=" + elements.length + "[");
        for (int i = 0; i < size; i++) {
            sb.append(elements[i]);
            if (i != size - 1)
                sb.append(",");
        }
        sb.append("]");
        return sb.toString();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容

  • 1. 什么是数据结构 ? 数据结构是计算机存储组织数据的方式。 1.1 线性结构 数组、链表、栈、队列、哈希表。 ...
    itlu阅读 335评论 0 2
  • 02-数据结构与动态数组 什么是数据结构 数据结构是计算机存储、组织数据的方式 线性结构示意图 树形结构示意图 图...
    ducktobey阅读 372评论 1 0
  • 动态数组:扩容与缩容 动态数组 是一种动态存储的线性表,所有元素的内存地址都是连续的。 很多语言的开发都需要用到数...
    YYFast阅读 1,739评论 0 0
  • 0. 序言 数组是线性表的代表,是很多复杂数据结构的底层实现;对数组的特性认识越深刻,对学习和设计复杂的数据结构大...
    付凯强阅读 1,104评论 2 2
  • 什么是数据结构? 接下来我们手写一个动态数组 首先动态数组的接口设计如下: 实现代码如下: #import ...
    topCui阅读 523评论 0 0