数组 [数据结构][Java]

数组

我们对数组的增删改查操作的时间复杂度为O(1)或者O(n)
  • 即使我们在数组的删除操作的时候是删除某个元素,这个时候只是告诉我们元素值,然后让我们删除数组中第一个遇到数值和指定元素值大小相等的元素,这个时候我们先要找到位置,然后进行删除,这个时候是要进行一个双层的for循环,但是这个删除操作的时间复杂度还是O(n) —> 因为这个时候我们的内层for循环只有在外层for循环执行最后一次的时候才会执行 ( 因为我们的内层for循环在一个if语句中,这个if语句只有当我们的外层for循环执行最后一次的时候才满足 ) —> 这个时候时间复杂度就是一个O(n)
我们的数组扩容一般都是扩容为原来的两倍
  • 我们的数组扩容的方法时在数组增加元素的方法中调用的, 并且我们也知道我们的数组扩容的时候的时间复杂度为O(n)
    • (因为我们的数组扩容就是创建一个新的数组,然后将我们的原本数组中的内容复制到我们的新数组中来,这个时候数组内容复制的操作就要使用到for循环,时间复杂度就是O(n) , 最后我们将数组内容复制到新数组之后不要忘记让我们的arr 引用指向我们的新数组 )
  • 但是我们可以发现我们的在执行增加元素的方法的时候并不是每次都执行扩容的操作,这个时候我们要如何计算我们的增加元素的方法的时间复杂度?
    • 这个时候就要使用到" 均摊复杂度 "
      • 那么什么是均摊复杂度? 我们这个时候就以上面的问题为例(假设这个时候我们是往数组的末尾添加元素):
        • 我们的数组容量如果为n那么我们每当执行n+1次的时候才会使用到一次扩容方法,这个时候扩容方法中执行的次数是n次,我们的原本的向末尾添加元素的方法的每次是执行一次,而我们每当执行n+1次向默认添加元素的操作之后就要执行一次数组扩容,那么将一次数组扩容中执行的n次操作"均摊"到我们的n+1次操作中,这个时候就是我们每次执行向末尾添加元素的操作的时间复杂度就是 2n+1/n , 也就是2 + 1/n,那么时间频度就是T(2 + 1/n),但其实时间复杂度还是O(1)
我们的数组一般都是当数组内元素实际只占用了数组容量的四分之一的时候才进行缩容,缩容为原来的数组容量的二分之一
  • 那么为什么明明是缩容为原来数组的二分之一,但是我们却是在占据容量只有四分之一的时候才缩容?
    • 这个时候我们要避免复杂度震荡的问题出现:
      • 那么什么是复杂度震荡的问题?
        • 就是如果我们是每次当我们的数组中实际元素占用了二分之一的时候就进行缩容,这个时候没有一个预留的缓冲空间, 这个时候就有可能是当我们刚刚数组中的实际存储元素占了数组容量二分之一的时候我们就进行了缩容,这个时候一旦一缩容,数组就是一个满的状态了,假如这个时候我们再向数组中添加元素,这个时候立马又会发生数组扩容,然后我们扩容之后数组的容量刚刚又是数组容量的二分之一了,这个时候如果我们又删除一个,这个时候我们又会进行一次缩容的操作,如果一直这样循环下去,这种情况就是一种时间复杂度震荡的问题:
          • 我们通过均摊时间复杂度的方式计算出我们的删除操作(删除末尾元素)和添加元素(向末尾添加)的操作的时间复杂度其实都是n(1), 但是如果是遇到了上面的情况,这个时候增加元素和删除元素的操作中每次都执行了扩容操作,这个时候时间复杂度就从O(1)变为了O(n) ----> 我们将这种现象就称之为 “时间复杂度震荡”
注意: 让我们的arr引用指向我们的新数组的时候如果形参位置也是arr,这个时候我们一定要使用this表名是给属性arr赋值,而不是给形参arr赋值
这里我们给出我们的自定义的一个数组类(这个数组类中使用了泛型,并且这个数组类中提供了很多操作自定义数组的方法):
package com.ffyc.util;

import org.junit.Test;

import java.util.Arrays;

/*
我们将我们的数组类创建成为一个泛型类: 那么的数组中就可以存放任何类型的数据了(只能是引用类型,如果是基本类型那么就包装为引用类型)
 */
public class MyArray<T> {
    private T [] arr;
    private int size;
    private int capacity;

    //提供无参构造器
    public MyArray(){
        this(100);
    }

    //提供有参构造器
    public MyArray(int capacity) {
        //注意: 我们不能直接声明泛型类型的数组, 如果我们想要声明泛型类型的数组,这个时候我们就要先将我们的数组声明为Object [] ,然后再通过T[]
        //强转为T []
        this.arr = (T [])new Object[capacity];
        this.size = 0;
        this.capacity = capacity;
    }

    //向数组的开头增加元素
    public void addHead(T val){
        //直接调用我们的插入到指定位置的方法即可
        add(0,val);
    }

    //向数组的末尾添加元素
    public void addLast(T val){
        //直接调用我们的插入到指定位置的方法即可
        add(size,val);
    }

    //将元素插入到数组中的指定位置
    public void add(int index, T val){
        //判断我们插入的位置是否合法
        if(index < 0 || index > size){
            throw new IllegalArgumentException("插入位置不合法!");
        }

        //判断我们的数组是否已满,如果已满就进行一个扩容,将我们的数组扩容为原来的容量的两倍
        if(size == capacity){
            resize(arr,capacity*2);
        }

        //将我们插入位置以及后面的元素全部后移
        for(int i = this.size; i>index; i--){
            arr[i] = arr[i-1];
        }
        arr[index] = val;

        this.size+=1;
    }

    //删除元素的方法(根据元素的值删除数组中第一个出现该值的元素)
    public void delete(T val){
        //先查询出我们的指定元素出现的位置
        for(int i = 0; i<this.size; i++){
            if(arr[i].equals(val)){
                //查询到我们想查询的值的位置之后我们这里就进行要一个删除操作,那么就要先让让从删除元素的下一个元素开始让元素值前移
                for (int j = i; j<this.size - 1; j++){
                    arr[j] = arr[j+1];
                }
                this.size-=1;

                //删除之后当数组中的实际元素减少之后判断是否实际元素的容量为数组容量的四分之一,如果到了四分之一,那么就进行缩容
                //并且我们要保证我们的数组最小容量为1
                if (size < capacity/4 && capacity/2 > 1){
                    suorong(arr,capacity/2);
                }
            }
        }
    }

    //查询数组中的有效元素的个数
    public int getSize(){
        return this.size;
    }

    //查询你数组的容量大小
    public int getCapacity(){
        return this.capacity;
    }

    //查询某个元素的下标并返回
    public int getIndex(T val){
        for (int i = 0; i < arr.length; i++) {
            if (val.equals(arr[i])){
                return i;
            }
        }
        //如果找不到就返回一个-1
        return -1;
    }

    //通过索引查询数组中对应位置的元素值
    public T getElement(int index){
        if (index < 0 || index > arr.length - 1){
            throw new IllegalArgumentException("查询位置不合法!");
        }
        return arr[index];
    }

    //数组扩容的方法
    private void resize(T [] arr,int newCapacity){
        //创建一个新的数组
        T [] newArr = (T [])new Object[newCapacity];

        //将旧数组中的内容复制到新数组中
        for (int i = 0; i < size; i++) {
            newArr[i] = arr[i];
        }
        //复制完之后一定要让我们的arr引用指向我们的新数组
        this.arr = newArr;

        //改变容量
        capacity = newCapacity;
    }

    //数组缩容的方法
    private void suorong(T [] arr,int newCapacity){
        //创建一个新的数组
        T [] newArr = (T[])new Object[newCapacity];


        //将旧数组中的内容复制到新数组中
        for (int i = 0; i < size; i++) {
            newArr[i] = arr[i];
        }

        //将我们的arr引用指向我们的新数组
        /*
        注意: 让我们的arr引用指向我们的新数组的时候如果形参位置也是arr,这个时候我们一定要使用this表名是给属性arr赋值,而不是给形参arr赋值
         */
        this.arr = newArr;

        //改变容量
        capacity = newCapacity;

    }

    public String toString(){
         return Arrays.toString(arr);
    }


}
class Test2{
    //测试方法

    public static void main(String[] args) {
            MyArray myArray = new MyArray(3);

            System.out.println(myArray.getCapacity());
        System.out.println(myArray.toString());
            myArray.addLast(1);
            myArray.addLast(2);
            myArray.addLast(3);
            myArray.addLast(3);
            myArray.addLast(3);
            System.out.println(myArray.getCapacity());
        System.out.println(myArray.toString());

            myArray.delete(1);
            myArray.delete(2);
            myArray.delete(3);
            myArray.delete(3);
            myArray.delete(3);
            myArray.delete(3);

            System.out.println(myArray.getCapacity());
        System.out.println(myArray.toString());

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值