美文网首页
动态数组

动态数组

作者: 不服输的小蜗牛 | 来源:发表于2020-06-11 23:23 被阅读0次

什么是动态数组?
大家都清楚,java中的数组在创建的时候是需要传入固定大小值的,如果我们添加的数据过多就会角标越界。
动态数组内部是通过java自带的数组来实现的。当添加数据时发现数据个数已经超过了当前数组的最大长度,创建当前数组两倍大小的数组再存储。当数据个数小于总长度的1/4时,为了节省空间,缩小为原来长度的1/2,用户调用无感知就好像我们这个数组可以无限存储数据一样。

1.创建动态数组类

public class Array<E> {
    private E[] data;//真正记录数据的数组
    private int size;//记录当前一共有多少数据

    public Array(int capacity) {
        data = (E[]) new Object[capacity];
    }

    public Array() {
        this(10);
    }

    /**
     * 获取当前数组大小
     * @return
     */
    public int getSize() {
        return size;
    }

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

    /**
     * 获取数组容量
     * @return
     */
    public int getCapacity(){
        return data.length;
    }

 @Override
    public String toString() {
        StringBuilder stringBuffer =new StringBuilder();
        stringBuffer.append(String.format("capacity: %d ,size: %d  [",getCapacity(),getSize()));
        for (int i = 0; i < size; i++) {
            if(i!=size-1){
                stringBuffer.append(data[i]+",");
            }else{
                stringBuffer.append(data[i]+"]");
            }
        }
        return stringBuffer.toString();
    }
}

以上代码创建个动态数组,然后内部是通过java数组来实现的存储,默认创建大小是10,也可以自定义传初始大小。然后是添加了一些简单的方法。
\color{#FF0000}{注意:}
new Object[]的时候一定不能导包,因为org.omg.CORBA.Object 下也有个Object类,如果你是提示输入的话最好看清楚包。

2.增加添加数据的方法

 /**
     * 指定index位置添加数据,其实就是把index位置以后的数据向后平移一个下标。然后再把E添加到index位置
     *
     * @param index
     * @param e
     */
    public void add(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("角标越界");
        }
        //如果当前数组已经没有空间去存储更多的数据,需要扩容
        if (size == getCapacity()) {
            resize(getCapacity() * 2);
        }

        for (int i = size - 1; i >= index; i--) {
            data[i + 1] = data[i];
        }

        data[index] = e;
        size++;
    }

    /**
     * 重新设置数组大小
     * @param newCapacity
     */
    public void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

    /**
     * 数组首添加元素
     * @param e
     */
    public void addFirst(E e){
        add(0,e);
    }
    /**
     * 数组尾添加元素
     * @param e
     */
    public void addLast(E e){
        add(size,e);
    }

3.增加删除元素的方法,删除指定位置的元素,其实就是把指定位置以后的所有内容都向前移动一个角标。

/**
 * 删除制定位置元素,其实就是index位置以后的元素都向前移动一个下标
 *
 * @param index
 * @return
 */
public E delete(int index) {
    if (index < 0 || index >= size) {
        throw new IllegalArgumentException("角标越界");
    }

    for (int i = index; i < size; i++) {
        data[i] = data[i + 1];
    }
    //释放内存
    data[size] =null;
    size--;

    //这里也许您会有疑问,为什么不是小于1/2的时候去设置呢?
    // 如果说在边界条件下正好我们的数据为原来数据的1/2,然后我们缩容数组大小,
    // 下次我们再次增加的时候发现数组容量又不足,我们又需要扩容,频繁这样操作就会,
    // 多次执行创建新数组的操作。所以在内容小于1/4时我们再去缩容是比较理性的。
    if (size < getCapacity() / 4 && getCapacity() / 2 != 0) {
        resize(getCapacity() / 2);
    }

    return data[index];
}

/**
 * 删除首元素
 * @return
 */
public E deleteFirst(){
    return delete(0);
}

/**
 * 删除尾元素
 * @return
 */
public E deleteLast(){
    return delete(size-1);
}

4.更新指定位置元素

public void update(int index, E e) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("角标越界");
        }

        data[index] = e;

    }

5.查找指定位置元素

  public E get(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("角标越界");
        }

        return data[index];
    }

    public E getFirst(){
        return get(0);
    }

    public E getLast(){
        return get(size-1);
    }

6.是否包含某个元素

public boolean contains(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e)) {
                return true;
            }
        }

        return false;
    }

7.查找元素的位置

public int findIndex(E e){
        for (int i = 0; i < size; i++) {
            if(e.equals(data[i])){
                return i;
            }
        }

        return -1;
    }

8.删除指定元素

public void deleteElement(E e){
        int index = findIndex(e);
        if(index!=-1){
            delete(index);
        }
    }

所有代码




public class Array<E> {
    private E[] data;//真正记录数据的数组
    private int size;//记录当前一共有多少数据

    public Array(int capacity) {
        data = (E[]) new Object[capacity];
    }

    public Array() {
        this(10);
    }
    
    public Array(E[] arr){
        data = (E[])new Object[arr.length];
        for(int i =0; i<arr.length;i++){
            data[i] = arr[i];
        }
        size = arr.length;
    }
    /**
     * 获取当前数组大小
     *
     * @return
     */
    public int getSize() {
        return size;
    }

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

    /**
     * 获取数组容量
     *
     * @return
     */
    public int getCapacity() {
        return data.length;
    }

    /**
     * 指定index位置添加数据,其实就是把index位置以后的数据向后平移一个下标。然后再把E添加到index位置
     *
     * @param index
     * @param e
     */
    public void add(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("角标越界");
        }
        //如果当前数组已经没有空间去存储更多的数据,需要扩容
        if (size == getCapacity()) {
            resize(getCapacity() * 2);
        }

        for (int i = size - 1; i >= index; i--) {
            data[i + 1] = data[i];
        }

        data[index] = e;
        size++;
    }

    /**
     * 重新设置数组大小
     *
     * @param newCapacity
     */
    public void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

    /**
     * 数组首添加元素
     *
     * @param e
     */
    public void addFirst(E e) {
        add(0, e);
    }

    /**
     * 数组尾添加元素
     *
     * @param e
     */
    public void addLast(E e) {
        add(size, e);
    }

    /**
     * 删除制定位置元素,其实就是index位置以后的元素都向前移动一个下标
     *
     * @param index
     * @return
     */
    public E delete(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("角标越界");
        }

        for (int i = index; i < size; i++) {
            data[i] = data[i + 1];
        }
        //释放内存
        data[size] = null;
        size--;

        //这里也许您会有疑问,为什么不是小于1/2的时候去设置呢?
        // 如果说在边界条件下正好我们的数据为原来数据的1/2,然后我们缩容数组大小,
        // 下次我们再次增加的时候发现数组容量又不足,我们又需要扩容,频繁这样操作就会,
        // 多次执行创建新数组的操作。所以在内容小于1/4时我们再去缩容是比较理性的。
        if (size < getCapacity() / 4 && getCapacity() / 2 != 0) {
            resize(getCapacity() / 2);
        }

        return data[index];
    }

    /**
     * 删除首元素
     *
     * @return
     */
    public E deleteFirst() {
        return delete(0);
    }

    /**
     * 删除尾元素
     *
     * @return
     */
    public E deleteLast() {
        return delete(size - 1);
    }

    /**
     * 更新指定位置元素
     *
     * @param index
     * @param e
     */
    public void update(int index, E e) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("角标越界");
        }

        data[index] = e;

    }




    /**
     * 查找指定位置元素
     *
     * @param index
     * @return
     */
    public E get(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("角标越界");
        }

        return data[index];
    }


    public E getFirst(){
        return get(0);
    }

    public E getLast(){
        return get(size-1);
    }

    public int findIndex(E e){
        for (int i = 0; i < size; i++) {
            if(e.equals(data[i])){
                return i;
            }
        }

        return -1;
    }
    /**
     * 是否包含某个元素
     * @param e
     * @return
     */
    public boolean contains(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 删除指定元素
     * @param e
     */
    public void deleteElement(E e){
        int index = findIndex(e);
        if(index!=-1){
            delete(index);
        }
    }

    public void swap(int i,int j){
        if(i<0 || i>=size ||j<0 ||j>=size){
            throw new IllegalArgumentException("index error");
        }
            E temp = data[i];
            data[i] = data[j];
            data[j] = temp;

    }

    @Override
    public String toString() {
        StringBuilder stringBuffer = new StringBuilder();
        stringBuffer.append(String.format("capacity: %d ,size: %d  [", getCapacity(), getSize()));
        for (int i = 0; i < size; i++) {
            if (i != size - 1) {
                stringBuffer.append(data[i] + ",");
            } else {
                stringBuffer.append(data[i] + "]");
            }
        }
        return stringBuffer.toString();
    }
}


方法 时间复杂度
addLast() O(1)
addFirst() O(n)
removeFist() O(n)
removeLast() O(1)
update(index) O(1)
contains() O(n)

扩容操作

在添加和删除元素时,会出现一种情况:扩容。根据实现的代码可以看出resize方法的时间复杂度在最坏的情况下为O(n)。但是它并不是每次添加或删除元素就会进行扩容,那么它出现的概率是多少呢?

在代码中,我们数组默认的长度为10,也就是在经过11次addLast操作后会触发resize,总共了进行了21次基本操作。也就是说,每一次addLast操作,约等于2次基本操作(21 / 11 ≈ 2)。以此可以推出:假设长度为n,n+1次addLast会触发resize,进行基本操作的次数为2,这样均摊计算的话,resize的时间复杂度为O(1)。很显然,均摊计算(amortized time complexity)比计算最坏情况有意义。

复杂度震荡

当我们调用addLast后,需要resize,此时,addLast的时间复杂度为O(n),紧接着,我们又调用removeLast,又会触发resize,removeLast的时间复杂度为O(n);不停地这样操作就造成了复杂度的震荡。也就是removeLast时,resize被触发的过于着急。解决这个问题的关键点就在于什么时候缩容。我们可以在当前元素个数为数组长度的1/4时才去缩容,这样能保证缩完容后再马上去调用addLast也不会马上resize。

相关文章

  • 20_总结

    一、动态数组 普通动态数组 环形动态数组 接口设计 int size(){} // 元素的数量 boolean i...

  • C语言动态数组

    一维动态数组 二维动态数组

  • C语言 泛型动态数组

    泛型实现思路:万能指针void *动态数组实现思路:动态进行数组内存的扩容 realloc 泛型动态数组 数组可以...

  • C++ 动态顺序表的实现(更新中)

    动态数组与数组相似,但是动态数组的大小可以在运行时动态修改。动态数组的元素占用连续的内存块,一旦创建,就无法更改其...

  • 笨办法学C 练习34:动态数组

    练习34:动态数组 原文:Exercise 34: Dynamic Array 译者:飞龙 动态数组是自增长的数组...

  • VBA之数组

    数组的声明 一维数组 二维数组 动态数组

  • 数据结构大纲

    1、线性表 1.1、数组 1.1.1、简介 数组是一段连续的内存 1.1.2、动态数组 有动态扩容功能和动态缩容功...

  • 关于ArrayList

    ArrayList简介 ArrayList内部是以动态数组存放数据的,所谓动态数组,不是数组本身可以改变长度,而是...

  • 2018-11-28

    vector容器。 vector类称为向量类,实现了动态数组,用于元素数组动态变化的对象数组。同数组一样,vect...

  • C++ 创建动态二维数组

    有时候数组过大,栈放不下,可以利用动态分配生成动态数组 动态创建数组时一定要记得结束程序时释放内存。

网友评论

      本文标题:动态数组

      本文链接:https://www.haomeiwen.com/subject/oemitktx.html