动态数组+算法动画演示

文章详细介绍了Java中的数组,包括数组的基本概念、固定长度的限制、如何通过索引进行访问以及二次封装创建自定义数组类。讨论了数组的优点——快速查询,同时提到了在不同场景下的适用性。文章还涉及了泛型类的实现,允许存储任意类型的数据。此外,文章讲解了动态数组的概念,讨论了数组的扩容策略和性能分析,以及如何通过延迟resize(lazy)来防止复杂度的震荡。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数组

不要小瞧数组,数组有很多东西值得我们挖掘。

把数据弄成一排进行存放

image-20230308212258690

在java中我们的格式是要求固定的。当然在一些语言中在一个数组中可以存储不同类型。

索引是一个很重要的概念,索引从0开始。最后一个元素是N-1

如果我们拥有索引,我们就可以直接进行访问。

数组拥有局限性,我们在开辟空间的时候必须指定长度

二次封装创建属于我们自己的类

索引可以有语义,也可以没有语义

image-20230308213220247

数组最大的优点:快速查询

数组最好应用于索引有语义的情况

但并非所有有语义的索引都适用于数组

比如身份证号:11100544846515156

这个空间是在太大了

数组也可以处理索引没有语义的情况

在这一章中,我们主要处理索引没有语义的情况数组的使用

  • 索引没有语义,如何表示没有元素

  • 如何添加元素?如何删除元素?

  • 基于java的数组,二次封装属于我们自己的数组类

image-20230308213457639

数组 有capacity,容量。 数组中实际有多少元素用sieze表示

public class Array{
    private int[] data;
    private int size;
    //构造函数,传入数组的容量capacity构造Array
    public Array(int capacity){
        data = new int[capacity];
        size = 0;
    }
    //无参数的构造函数,默认数组的容量capacity=10
    public Array(){
        this(10);
    }
    //获取数组中的元素个数
    public int getSize(){
        return size;
    }
    //获取容量
    public int getCapacity(){
        return data.length;
    }
    //判断数组返回是否为空
    public boolean isEmpty(){
        return size==0;
    }
    
} 

向数组中添加元素

向数组末添加元素

image-20230308215421376

image-20230308215455585

添加了一个元素66,记得维护size

public void addLast(int e){
    //判断是否超过数组的容量
    if(size == data.length)
        throw new IlleaglArgumentException("addlast failed, array is full");
    data[size]=e;
    size++;
    //data[size++]=e
    //add(size,e);//可以直接调用add方法
}

在这里插入图片描述

在index个位置插入一个新元素e
public void add(int index,int e){
    //判断是否超过数组的容量
    if(size == data.length)
        throw new IlleaglArgumentException("add failed, array is full");
    if(index < 0|| index > size)
        throw new IlleaglArgumentException("add failed, require size>=0 && <capacity");
    for(int i = size - 1; i >= index; i--)
        data[i+1]=data[i];
    data[index]=e;
    size++;
}
//复用机制
public void addFirst(int e){
    add(0,e);
}

数组查询元素和修改元素

@Override
public String toString(){
    StringBuilder res = new StringBuilder();
    res.append(String.format("Array: size = %d, capacity = %d\n"),size,data.length);
    res.append('[');
    for(int i = 0 ; i <size; i++)
        res.append(data[i]);
    	if(i!=size-1)
            res.append(", ");
    res.append(']');
    return res.toString();
}
//获取index索引位置的元素
int get(int index){
    if(index<0 ||index >= size)
        throw new IllegalArgumentException("Get failed,Index is illegal");
    return data[index];
}
//修改index索引位置的元素为e
void set(int index,int e){
     if(index<0 ||index >= size)
        throw new IllegalArgumentException("Get failed,Index is illegal");
    data[index] = e;
}

数组中的包含、搜索和删除元素

public boolean contains(int e){
    for(int i = 0; i< size;i++){
        if(data[i]==e)return true;
        return false;
    }
}
//查找数组中元素e所在的索引,如果不存在元素e,则返回-
public int find(int e){
     for(int i = 0; i< size;i++){
        if(data[i]==e)return i;
        return -1;
    }
}

在这里插入图片描述

public int remove(int index){
     if(index<0 ||index >= size)
        throw new IllegalArgumentException("Remove failed,Index is illegal");
    int ret = data[index];
    for(int i =index+1;i<size;i++){
        data[i-1]=data[i];
    size--;
    }
    return ret;
}
//从数组中删除第一个元素,返回删除的元素
public int removeFirst(){
    return remove(0);
}
//从数组中删除最后一个,返回删除的元素
public int removeLast(){
    return remove(size-1);
}
//从数组中删除元素,这是删除第一个,但是可以设计删除所有的元素
public void removeElement(int e){
    int index = find(e);
    if(index!=-1)
        remove(index);
}

泛型类

使用泛型,应该能够存储任意类型,还应该包括用户自定义的类型。

这和之前的线性查找法和排序查找法是一样的。数据结构,使用泛型的话都需要创建泛型类

改造如下

public class Array<E>{
    private E[] data;
    private int size;
    //构造函数,传入数组的容量capacity构造Array
    public Array(int capacity){
        data =(E[])new Obeject[capacity];
        
        size = 0;
    }
    //无参数的构造函数,默认数组的容量capacity=10
    public Array(){
        this(10);
    }
    //获取数组中的元素个数
    public int getSize(){
        return size;
    }
    //获取容量
    public int getCapacity(){
        return data.length;
    }
    //判断数组返回是否为空
    public boolean isEmpty(){
        return size==0;
    }
    
} 
//获取index索引位置的元素
E get(int index){
    if(index<0 ||index >= size)
        throw new IllegalArgumentException("Get failed,Index is illegal");
    return data[index];
}
//修改index索引位置的元素为e
void set(int index,E e){
     if(index<0 ||index >= size)
        throw new IllegalArgumentException("Get failed,Index is illegal");
    data[index] = e;
}
在index个位置插入一个新元素e
public void add(int index,E e){
    //判断是否超过数组的容量
    if(size == data.length)
        throw new IlleaglArgumentException("add failed, array is full");
    if(index < 0|| index > size)
        throw new IlleaglArgumentException("add failed, require size>=0 && <capacity");
    for(int i = size - 1; i >= index; i--)
        data[i+1]=data[i];
    data[index]=e;
    size++;
}
//复用机制
public void addFirst(E e){
    add(0,e);
}
public boolean contains(E e){
    for(int i = 0; i< size;i++){
        if(data[i].equlas(e))return true;
        return false;
    }
}
//查找数组中元素e所在的索引,如果不存在元素e,则返回-
public E find(E e){
     for(int i = 0; i< size;i++){
        if(data[i].equals(e))return i;
        return -1;
    }
}
public E remove(int index){
     if(index<0 ||index >= size)
        throw new IllegalArgumentException("Remove failed,Index is illegal");
    int ret = data[index];
    for(int i =index+1;i<size;i++){
        data[i-1]=data[i];
    
    }
    size--;
    data[size]=null;//java的自动回收机制,但这句话也不是必须的,loitering objects闲的物品!=memory leak不等于内存泄漏
    return ret;
}
//从数组中删除第一个元素,返回删除的元素
public E removeFirst(){
    return remove(0);
}
//从数组中删除最后一个,返回删除的元素
public E removeLast(){
    return remove(size-1);
}
//从数组中删除元素,这是删除第一个,但是可以设计删除所有的元素
public void removeElement(E e){
    int index = find(e);
    if(index!=-1)
        remove(index);
}

动态数组

动态数组扩容,先复制一个二倍的空间,然后将原data里的数据通过循环便利复制到newdata数组中,将data的引用指向new data,这个时候原数组会被java的垃圾回收机制回收。
在这里插入图片描述

通过改造后的add函数
在index个位置插入一个新元素e
public void add(int index,E e){
    
    if(index < 0|| index > size)
        throw new IlleaglArgumentException("add failed, require size>=0 && <capacity");
    //判断是否超过数组的容量,我们进行扩容
    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++;
}    

这里扩容2倍是和当前元素有多少是有关的,是有数量级关系的。如果不是同一个数量级,比如10000 和10,加上常数的话,性能不是很高。

当然,这里的2倍也可以换成1.5倍等等,参考java的colleciton类

private void resize(int new Capacity){
    E[] newData =  (E[])new Object[newCapacity];//java不支持直接泛型类,需要转类型
    for(int i = 0 ; i < size; i++)
        newData[i] = data[i];
    data = newData;
}
public int remove(int index){
     if(index<0 ||index >= size)
        throw new IllegalArgumentException("Remove failed,Index is illegal");
    int ret = data[index];
    for(int i =index+1;i<size;i++){
        data[i-1]=data[i];
   
    }
     size--;
    if(size == data.length/2)
        resize(data.length/2);//节省数组空间
    return ret;
}

简单的复杂度分析

  • 添加操作
    • addLast(e) O(1)的时间复杂度
    • addFirst(e) O(n)的复杂度
    • add(index, e) O(n)
      • 严格计算需要一些概率论知识

综合来看是一个O(n)级别的算法

时间复杂度不能按照最好的情况来解决,而应该按照最坏的情况。

  • 删除操作

    • removeLast(e) O(1)
    • removeFirst(e) O(n)
    • remove(index, e) O(n)

    O(n)级别的

  • 修改操作

    • set(index,e) O(1)
  • 查找操作

    • get(index) o(1)

    • contains(e) O(n)

    • find(e) o(n)

      综合来看:

image-20230309095627140

均摊复杂度和防止复杂度的震荡

image-20230309095738196

我们不可能每一次添加元素都触发这个resize操作,比如添加10个元素,需要到10才会触发resize,我们不可能每次添加元素都触发resize,这里使用最坏情况是有点不合理的

假设当前capacity = 8,并且每一次添加操作都使用addLast

image-20230309095909324

平均,每次addLast操作进行2次基本操作

假设capacity=n,n+1次addLast操作,触发resize,总共进行2n+1次基本操作

平均,每次addLast操作,进行2次基本操作

这样的均摊计算,时间复杂度是O(1)的!

这个例子里,这样均摊计算是更有意义的

amortized time complexity

相对比较耗时的操作是可以分摊的

同理,我们看removeLast,均摊复杂度也为O(1)

但是,当我们同时看addLast和removeLast操作

在这里插入图片描述

复杂度震荡出现问题的原因:removeLast时resize过于着急(Eager)

解决方案:lazy

在这里插入图片描述

当size==capacity/4,才将capacity减半

public int remove(int index){
     if(index<0 ||index >= size)
        throw new IllegalArgumentException("Remove failed,Index is illegal");
    int ret = data[index];
    for(int i =index+1;i<size;i++){
        data[i-1]=data[i];
   
    }
     size--;
    if(size == data.length/4&&data.length/2!=0)//防止data.length=1,这样会导致长度为0
        resize(data.length/2);//节省数组空间
    return ret;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值