首页 > 其他分享 >数据结构——动态数组

数据结构——动态数组

时间:2023-02-01 18:05:20浏览次数:53  
标签:index 动态 return 数组 数据结构 data public size

简介

数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。因此可以通过索引(Index)计算出某个元素的地址。

 

数组特点

  • 索引(即下标) 一般从0开始,如java, C/C++。
  • 长度固定,在申请时长度固定。内存连续,在内存中则是申请一块连续的固定大小的空间。
  • 数组有一维数组和多维数组,数组元素可以是基本数据类型(Primitive),也可以是对象引用(Reference)。
  • 随机访问,能够根据位置(下标)直接访问到元素。

线性表(Linear List)

零个或多个数据元素的有限序列。每个线性表上的数据最多只有前和后两个方向。其实除了数组、链表、队列、栈等也是线性表结构。

非线性表

它相对立的概念是非线性表,比如二叉树、堆、图等。之所以叫非线性,是因为,在非线性表中,数据之间并不是简单的前后关系。

连续的内存空间和相同类型的数据:这两个限制也让数组的很多操作变得非常低效,比如要想在数组中删除、插入一个数据,为了保证连续性,就需要做大量的数据搬移工作。但是数组的随机访问效率确实十分的高。

插入

假设数组的长度为 n,将一个数据插入到数组中的第 k 个位置。为了把第 k 个 位置腾出来,给新来的数据,我们需要将第 k~n 这部分的元素都顺序地往后挪一位。

数组中插入数据

如果在数组的末尾插入元素,那就不需要移动数据了,这时的时间复杂度为 O(1)。 数组末尾插入数据

但如果在数组的开头插入元素,那所有的数据都需要依次往后移动一位,所以最坏时间复杂度是 O(n)。 因为我们在 每个位置插入元素的概率是一样的,所以平均情况时间复杂度为 (1+2+…n)/n=O(n)。

 

如果数组中的数据是有序的,我们在某个位置插入一个新的元素时,就必须按照刚才的方法搬移 k 之后的数据。但是,如果数组中存储的数据并没有任何规律,数组只是被当作一个存储数据的集 合。在这种情况下,如果要将某个数组插入到第 k 个位置,为了避免大规模的数据搬移,我们还有 一个简单的办法就是,直接将第 k 位的数据搬移到数组元素的最后,把新的元素直接放入第 k 个位 置。

快排思想 在特定场景下,在第 k 个位置插入一个元素的时间复杂度就会降为 O(1)。这个 处理思想在快排中会用。

删除

插入数据类似,我们要删除第 k 个位置的数据,为了内存的连续性,也需要搬移数据,不然中间就会出现空洞,内存就不连续了。在某些特殊场景下,我们并不一定非得追求数组中数据的连续性。如果我们将多次删除操作集中在一起执行,这种就类似于Java虚拟中的标记清除。

标记清除

当数组没有更多空间存储数据时,我们再触发执行一次真正的删除操作,这样就大大减少了删除操作导致的数据搬移。

容器

ArrayList 最大的优势就是可以将很多数组操作的细节封装起来。比如前面提到的数组 插入、删除数据时需要搬移其他数据等。

 

另外,它还有一个优势,就是支持动态扩容。数组本身在定义的时候需要预先指定大小,因为需要分配连续的内存空间。如果我们申请了大小为 10 的数组,当第 11 个数据需要存储到数组中时,我们就需要重新分配一块更大的空间,将原来的 数据复制过去,然后再将新的数据插入。

 

如果使用 ArrayList,我们就完全不需要关心底层的扩容逻辑,ArrayList 已经帮我们实现好了。每次 存储空间不够的时候,它都会将空间自动扩容为 1.5 倍大小。

 

不过,这里需要注意一点,因为扩容操作涉及内存申请和数据搬移,是比较耗时的。所以,如果事 先能确定需要存储的数据大小,最好在创建 ArrayList 的时候事先指定数据大小。

 

比如我们要从数据库中取出 10000 条数据放入 ArrayList。我们看下面这几行代码,你会发现,相比 之下,事先指定数据大小可以省掉很多次内存申请和数据搬移操作。

自定义实现ArrayList

public class Array<E> {

    private E[] data;

    private int size;

    public Array(){
        this(10);
    }

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

    /**
     * 获取数组中元素个数
     * @return
     */
    public int getSize(){
        return size;
    }

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

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

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

    /**
     * 数组头部新增元素
     * @param e
     */
    public void addFirst(E e){
        add(0, e);
    }

    /**
     * 在指定位置插入元素
     * @param index
     * @param e
     */
    public void add(int index, E e){
        if(index < 0 || index > size){
            throw new IllegalArgumentException("AddLast failed. require index >=0 and index <= size");
        }
        if(size == data.length){
            //扩容
            resize(2 * data.length);
        }

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

    /**
     * 数组扩容
     * @param newCapacity
     */
    private void resize(int newCapacity){
        E[] newData = (E[])new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

    /**
     * 获取指定索引位置的值
     * @param index
     * @return
     */
    public E get(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("Get failed. index is illegal.");
        }
        return data[index];
    }

    /**
     * 替换指定索引位置的值
     * @param index
     * @param e
     */
    public void set(int index, E e){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("Set failed. index is illegal.");
        }
        data[index] = e;
    }

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

    /**
     * 查找数组中元素e所在的索引,不存在元素e,返回-1
     * @param e
     * @return
     */
    public int find(E e){
        for (int i = 0; i < size; i++) {
            if(data[i].equals(e)){
                return i;
            }
        }
        return -1;
    }

    /**
     * 删除数组中index位置的元素, 并返回删除的元素
     * @param index
     * @return
     */
    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; i < size - 1; i++) {
            data[i] = data[i + 1];
        }
        size --;
        data[size] = null;
        if(size == data.length / 4 && data.length / 2 != 0){
            //当数组长度缩小为原数组的4分之一的时候才进行数组的缩容,
            //缩小为原数组的2分之一,预留空间,防止有数据添加导致扩容浪费性能
            resize(data.length / 2);
        }
        return ret;
    }

    /**
     * 删除数组中第一个元素
     * @return
     */
    public E removeFirst(){
        return remove(0);
    }

    /**
     * 删除数组中最后一个元素
     * @return
     */
    public E removeLast(){
        return remove(size - 1);
    }

    /**
     * 从数组中删除元素e
     * @param e
     */
    public void removeElement(E e){
        int index = find(e);
        if(index != -1){
            remove(index);
        }
    }

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

测试

public class ArrayDemo1 {

    public static void main(String[] args) {
        Array<Integer> array = new Array<Integer>();
        for (int i = 0; i < 10; i++) {
            array.addLast(i);
        }
        System.out.println(array);

        array.add(1,200);
        System.out.println(array);

        array.addFirst(-1);
        System.out.println(array);

        array.remove(2);
        System.out.println(array);

        array.removeElement(4);
        System.out.println(array);

        array.removeFirst();
        System.out.println(array);

        array.removeLast();
        System.out.println(array);
    }

}

时间复杂度总结

最后整理个表格,上述操作的时间复杂度大致如下,很容易理解就不详细解释了。

操作 平均时间复杂度 最坏条件时间复杂度
插入 O(n) O(n)
删除 O(n) O(n)
查找 O(n) O(n)
访问 O(1) O(1)

参考: https://blog.csdn.net/weixin_39084521/article/details/89820318

https://www.cnblogs.com/fanglongxiang/p/13034353.html

标签:index,动态,return,数组,数据结构,data,public,size
From: https://blog.51cto.com/u_14014612/6031719

相关文章

  • 数据结构——队列
    简介队列是是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。 队列是一种先进先出的线性表,简称FIFO允许插入的以端称为队尾,允许删除的一端被称为队头。入......
  • 数据结构——栈
    简介限定仅在表尾进行插入和删除操作的线性表。允许插入和删除的一端成为栈顶,另一端成为栈低,不含任何元素的栈成为空栈,栈又称为先进先出的线性表,简称LIFO结构。 栈的插......
  • 数据结构——链表
    链表数组要一块连续的内存空间来存储,对内存的要求比较高。如果我们申请一个100MB大小的数组,当内存中没有连续的、足够大的存储空间时,即便内存的剩余总可用空间大于100MB,......
  • JavaScript 数组去重
    JavaScript中有多种方法可以实现数组去重,下面是几种常用的方法:1、使用Set去重:Set数据结构中不能有重复元素,可以将数组转成Set类型,再转回数组。letarr=[1,2,3,4......
  • 高效NumPy操作,避免不必要数组复制
    了解NumPy的内部原理,避免不必要的数组复制来源于:​ ​​​​IPythonCookbook,SecondEdition​​​​​,by ​​​​CyrilleRossant​​​​▶  ​​CodeonGitHub......
  • 动态数组以及指针迭代器
    1#include<vector>//动态数组2#include<iostream>3usingnamespacestd;4vector<int>vec;//定义5intmain(){6intn;7cin>>n;8......
  • 1846.maximum element after decreasing and rearranging 减小和重新排列数组后的最大
    问题描述1846.减小和重新排列数组后的最大元素解题思路由于题目允许我们重新排列数组中的元素任意次,因此首先将数组排序,根据arr中第一个元素必须为1,以及相邻两元素的差......
  • Java基础系列三、数组
    数组概述及格式数组:同一种类型数据的集合。其实数组就是一个容器只要是容器,就得重点掌握数组的好处可以自动给数组中的元素从0开始编号,方便操作这些元素数组的定义......
  • cdr vb 动态定义数组 遍历对象里面包含群组的
    Function显示内容()DimsAsShapeDimssonAsShapeDimisonAsIntegerDimflagAsBooleanDimalAsIntegerIfActiveSelection.Shapes.count>0ThenDimsts()AsS......
  • 什么是最大子数组问题?
    本文首发自「慕课网」,想了解更多IT干货内容,程序员圈内热闻,欢迎关注!作者|慕课网精英讲师JdreamZhang最大子数组(MaxSubarray)问题,是计算机科学与技术领域中一种常见的算法问......