1.基础的线性数据结构:数组

一、基础的线性数据结构:数组

image-20231219201911893

1. 静态数组

1.1 随机读写性能很棒

  • 数组是线性数据结构,使用数组存储的数据会排成像一条线一样

image-20231219195026616

  • 用一组

连续的内存空间,来存储一组具有相同类型的数据

选址公式:

image-20231219195050730

int[] data = new int[5];

image-20231219195126222

data[2]元素内存地址=1000+2*4=1008

所以data[2]=0

随机访问数组中任一元素的时间复杂度为O(1)

将index=3的元素的值设置为4

    1. 先找到index=3的元素所在的内存地址=1000+3*4=1012
    2. 将内存地址为1012指向的内存值设置为4

随机读写数组中任一元素的时间复杂度为O(1)

结论:数组的随机读写性能非常的好

1.2 随机新增元素

image-20231219195459300

 public static int[] insertElement(int[] src,int index,int element){
     int length=src.length;
     int[] dest = new int[length+1];for (int i = 0; i < index; i++) {
         dest[i]=src[i];
     }
     dest[index]=element;
     for (int i = index; i < length; i++) {
         dest[i+1]=src[i];
     }
     return dest;
 }

时间复杂度为O(n),空间复杂度为O(n)

1.3 随机删除元素

image-20231219195520625

 public static int[] removeElement(int[] src,int index){
     int[] dest = new int[src.length-1];for (int i = 0; i < index; i++) {
         dest[i]=src[i];
     }for (int i = index; i < src.length-1; i++) {
         dest[i]=src[i+1];
     }
     return dest;
 }

空间复杂度为O(n),时间复杂度为O(n)

结论:相对于数组的随机读写,数组的随机删除和新增的性能不是很好

2. 二次封装静态数组

Java内置数组的缺点:

需要写一个ArrayUtils来实现以下功能:

  1. 没有新增元素的方法
  2. 没有删除元素的方法
  3. 没有判空方法
  4. 没有获取真实存储元素个数的方法
  5. 没有判断是否存在指定目标值的方法
  6. 例如:
 /**
      * 判断是否为空
      * @param arr
      * @return
      */
 public static boolean isEmpty(int[] arr){
     for(int num:arr){
         //此处假设等于0表示没有元素
         //这个是不严谨的,因为元素的值可能是0
         if(num!=0)
             return false;
     }
     return true;
 }

因此需要二次封装静态数组ArrayList

image-20231219195549287

成员变量包括:一个内置数组,capacity记录数组容量,size记录数组元素个数

2.1 新增操作

例如:把21插入到索引为1的位置

将index指向1,并将后面的元素都向后移一位(从最后一个元素开始往后移),最后size加1

public void add(int index,int e){
    if(size == data.length){
        throw new IllegalArgumentException("add failed,Array is full");
    }
    if(index<0 || index>size){
        throw new IllegalArgumentException("add failed,require index>=0 && index<=size");
    }
    //计算最差时间复杂度,循环代码运行最大次数
    //size=data.length && index=0
    //时间复杂度O(n)
    for(int i=size-1;i>=index;i--){
        data[i+1]=data[i];
    }
    data[index]=e;
    size++;
}

时间复杂度为O(n)

2.2 查询操作

  • 获取index索引位置的元素
public int get(int index){
    if(index<0||index>=size){
        throw new IllegalArgumentException("get failed,require index>=0 && index<=size");
    }
    return data[index];
}

时间复杂度为O(1)

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

时间复杂度为O(n)

  • 是否包含元素e
 public boolean contains(int target){
     for (int num : data) {
         if(num==target)
             return true;
     }
     return false;
 }

时间复杂度为O(n)

  • 重写toString()
 @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();
 }

2.3 修改操作

 public void set(int index,int e){
     if(index<0||index>=size){
         throw new IllegalArgumentException("set failed,require index>=0 && index<=size");
     }
     data[index]=e;
 }

时间复杂度为O(1)

2.4 删除操作

例如:删除索引为1的元素

将后面的元素覆盖掉前面的元素,size减1,并将当前位置的元素对象释放掉

public int remove(int index){
    if(index<0||index>=size){
        throw new IllegalArgumentException("remove failed,require index>=0 && index<=size");
    }
    //记录要删除的元素
    int res = data[index];
    //最差:index=0 && size=data.length
    //时间复杂度为O(n)
    for (int i = index+1; i < size; i++) {
        data[i-1]=data[i];
    }
    size--;
    //GC 清除不用的对象
    data[size]=null;
    return res;
}

//删除指定元素
public void removeElement(int e){
    //时间复杂度为O(n)
    int index = find(e);
    if(index!=-1){
        remove(index);
    }
}

2.5 使用泛型

 public class ArrayList<E> {
     private E[] data;
     private int capacity;
     private int size;public ArrayList(int capacity){
         this.data = (E[]) new Object[capacity];
         this.capacity=capacity;
         this.size=0;
     }public ArrayList(){
         this(15);
     }public boolean isEmpty(){
         return size==0;
     }public int getSize(){
         return size;
     }public int getCapacity(){
         return capacity;
     }/**
      * 新增操作:在指定位置插入一个元素
      * @param index
      * @param e
      */
     public void add(int index,E e){
         if(size == data.length){
             throw new IllegalArgumentException("add failed,Array is full");
         }
         if(index<0 || index>size){
             throw new IllegalArgumentException("add failed,require index>=0 && index<=size");
         }
         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 void addLast(E e){
         add(size,e);
     }/**
      * 查询操作:获取index索引位置的元素
      * @param index
      * @return
      */
     public E get(int index){
         if(index<0||index>=size){
             throw new IllegalArgumentException("get failed,require index>=0 && index<=size");
         }
         return data[index];
     }/**
      * 查找元素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索引位置的元素修改为新元素e
      * @param index
      * @param e
      */
     public void set(int index,E e){
         if(index<0||index>=size){
             throw new IllegalArgumentException("set failed,require index>=0 && index<=size");
         }
         data[index]=e;
     }/**
      * 删除操作:删除指定索引位置的元素
      * @param index
      */
     public E remove(int index){
         if(index<0||index>=size){
             throw new IllegalArgumentException("remove failed,require index>=0 && index<=size");
         }
         E res = data[index];
         for (int i = index+1; i < size; i++) {
             data[i-1]=data[i];
         }
         size--;
         //GC 清除不用的对象
         data[size]=null;
         return res;
     }public E removeFirst(){
         return remove(0);
     }public E removeLast(){
         return remove(size-1);
     }/**
      * 删除指定元素
      * @param e
      */
     public void removeElement(E e){
         int index = find(e);
         if(index!=-1){
             remove(index);
         }
     }public boolean contains(E target){
         for (E num : data) {
             if(num.equals(target))
                 return true;
         }
         return false;
     }@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();
     }
 }

3. 动态数组

3.1 扩容

扩容缩容三步法:

  1. 创建一个容量为newCapacity的临时数组
  2. 将原来数组中的元素拷贝到新数组中
  3. 将新数组覆盖老数组

image-20231219200238167

//改进前
public void add(int index,E e){
    if(index<0 || index>size){
        throw new IllegalArgumentException("add failed,require index>=0 && index<=size");
    }
    //扩容
    if(size == data.length){
        //1.创建一个容量为2倍于之前容量的临时数组
        int newCapacity = capacity<<1;
        E[] newData = (E[])new Object[newCapacity];
        //2.将原来数组中的元素拷贝到新数组中
        for (int i = 0; i < data.length; i++) {
            newData[i] = data[i];
        }
        //3.将新数组覆盖老数组
        data = newData;
        // bug 修复:将容量设置位新容量值
        capacity = newCapacity;
    }
    for(int i=size-1;i>=index;i--){
        data[i+1]=data[i];
    }
    data[index]=e;
    size++;
}

//改进后,扩容只需调用resize方法,将新容量传入即可
public void add(int index,E e){
    if(index<0 || index>size){
        throw new IllegalArgumentException("add failed,require index>=0 && index<=size");
    }
    //扩容
    if(size == data.length){
        resize(capacity<<1);
    }
    for(int i=size-1;i>=index;i--){
        data[i+1]=data[i];
    }
    data[index]=e;
    size++;
}

3.2 缩容

image-20231219200514122

image-20231219200516329

将扩容和缩容的公共部分提取成resize方法,如需改变容量,调用resize方法即可,将新的数组容量通过参数传入

 //扩容和缩容操作
 public void resize(int newCapacity){
     //1.创建一个容量为newCapacity的临时数组
     E[] newData = (E[])new Object[newCapacity];
     //2.将原来数组中的元素拷贝到新数组中
     //此处缩容会有数组下标越界异常,需注意!
     for (int i = 0; i < data.length; i++) {
         newData[i] = data[i];
     }
     //3.将新数组覆盖老数组
     data = newData;
     // bug 修复:将容量设置位新容量值
     capacity = newCapacity;
 }//删除操作
 public E remove(int index){
     if(index<0||index>=size){
         throw new IllegalArgumentException("remove failed,require index>=0 && index<=size");
     }
     E res = data[index];
     for (int i = index+1; i < size; i++) {
         data[i-1]=data[i];
     }
     size--;
     //GC 清除不用的对象
     data[size]=null;//如果size等于总容量的一半的话,则进行缩容
     //此处可能会出现时间复杂度震荡,需注意!
     if(size==data.length/2){
         resize(data.length>>1);
     }
     return res;
 }

3.3 缩容的bug修复

缩容的情况下如果使用data.length会报错,不能使用data.length

 //扩容和缩容操作
 public void resize(int newCapacity){
     //1.创建一个容量为newCapacity的临时数组
     E[] newData = (E[])new Object[newCapacity]; //7
     
     //2.将原来数组中的元素拷贝到新数组中
     
     //此处不需要将原先整个data数组复制到新数组中,只需要将里面的元素拷贝到新数组即可,元素个数size(此时size为总容量的一半),此时传入的newCapacity为data的一半,新数组的容量为newCapacity,如果将原先数组data.length都复制到新数组中时,会出现下标越界异常
     for (int i = 0; i < size; i++) {    //data.length:14
         newData[i] = data[i];
     }
     
     //3.将新数组覆盖老数组
     data = newData;
 }

3.4 均摊时间复杂度

addLast均摊时间复杂度:

在往数组最后添加元素时,如果数组满了,需要扩容,不会影响时间复杂度

正常情况下,addLast时间复杂度为O(1)

操作:第一次:56;第二次:12;第三次:33;第四次:24;第五次:45

第六次:复制…

第十一次:31

第十二次:data = newData;

image-20231219200554330

image-20231219200557179

addLast均摊时间复杂度:O(1)

removeLast均摊时间复杂度:O(1)

3.5 时间复杂度震荡

在边界时,不断插入元素或者删除元素,在缩容和扩容之间徘徊,会导致时间复杂度震荡

addLast时间复杂度O(n)

removeLast时间复杂度O(n) (扩容或者缩容都需要遍历数组)

解决办法:在缩容的时候,并非在size为容量一半的时候缩容,而是选择在1/4的时候缩容,尽可能减少时间复杂度震荡;另外需要注意原先数组的长度有可能不断的减少,有可能会小于 2,此时假如是1的话,1/2为0,传入的新容量就为0了,所以需要判断不等于0

 public E remove(int index){
     if(index<0||index>=size){
         throw new IllegalArgumentException("remove failed,require index>=0 && index<=size");
     }
     E res = data[index];
     for (int i = index+1; i < size; i++) {
         data[i-1]=data[i];
     }
     size--;//GC 清除不用的对象
     data[size]=null;//如果size等于总容量的四分之一的话,则进行缩容
     //此处可能会有时间复杂度震荡,为了避免,选择在1/4的时候进行缩容
     //注意:因为data.length有可能不断的减少,所以有可能小于2了,所以需要判断下
     if(size==data.length/4 && data.length/2!=0){
         resize(data.length>>1);
     }
     return res;
 }

4. 静态数组vs动态数组

  1. 动态数组使用相对方便
  2. 动态数组支持自动扩容缩容
  3. 动态数组申请内存耗费时间,没有静态数组性能好
  4. 动态数组不能存储基本类型的元素

5. 最终代码

 public class ArrayList<E> {
     private E[] data;
     private int capacity;
     private int size;public ArrayList(int capacity){
         this.data = (E[]) new Object[capacity];
         this.capacity=capacity;
         this.size=0;
     }public ArrayList(){
         this(15);
     }
     
     public ArrayList(E[] arr) {
         this.data = (E[])new Object[arr.length];
         for (int i = 0; i < arr.length; i++) {
             data[i] = arr[i];
         }
         size = arr.length;
         // bug 修复:加上 capacity 的初始化
         this.capacity = arr.length;
     }//判断是否为空
     public boolean isEmpty(){
         return size==0;
     }//获取数组元素个数
     public int getSize(){
         return size;
     }//获取容量
     public int getCapacity(){
         return capacity;
     }/**
      * 新增操作:在指定位置插入一个元素
      * 时间复杂度:O(n)
      * @param index
      * @param e
      */
     public void add(int index,E e){
         if(index<0 || index>size){
             throw new IllegalArgumentException("add failed,require index>=0 && index<=size");
         }
         //扩容
         if(size == data.length){
             resize(capacity<<1);
         }
         //计算最差时间复杂度,循环代码运行最大次数
         //size=data.length && index=0
         //时间复杂度O(n)
         for(int i=size-1;i>=index;i--){
             data[i+1]=data[i];
         }
         data[index]=e;
         size++;
     }//时间复杂度O(n)
     public void addFirst(E e){
         add(0,e);
     }//时间复杂度O(1),如果添加最后时数组满了,需要扩容,此时均摊时间复杂度
     public void addLast(E e){
         add(size,e);
     }//扩容和缩容操作
     public void resize(int newCapacity){
         //1.创建一个容量为newCapacity的临时数组
         E[] newData = (E[])new Object[newCapacity];
         //2.将原来数组中的元素拷贝到新数组中
         //缩容bug修复
         for (int i = 0; i < size; i++) {
             newData[i] = data[i];
         }
         //3.将新数组覆盖老数组
         data = newData;
         // bug 修复:将容量设置位新容量值
         capacity = newCapacity;
     }/**
      * 删除操作:删除指定索引位置的元素
      * @param index
      */
     public E remove(int index){
         if(index<0||index>=size){
             throw new IllegalArgumentException("remove failed,require index>=0 && index<=size");
         }
         //暂存要删除索引位置的元素
         E res = data[index];
         for (int i = index+1; i < size; i++) {
             data[i-1]=data[i];
         }
         size--;//GC 清除不用的对象
         data[size]=null;//如果size等于总容量的四分之一的话,则进行缩容
         //此处可能会有时间复杂度震荡,为了避免,选择在1/4的时候进行缩容
         //注意:因为data.length有可能不断的减少,所以有可能小于2了,所以需要判断下
         if(size==data.length/4 && data.length/2!=0){
             resize(data.length>>1);
         }
         return res;
     }//时间复杂度为O(n)
     public E removeFirst(){
         return remove(0);
     }//时间复杂度为O(1)
     public E removeLast(){
         return remove(size-1);
     }/**
      * 删除指定元素
      * 时间复杂度为O(n)
      * @param e
      */
     public void removeElement(E e){
         int index = find(e);
         if(index!=-1){
             remove(index);
         }
     }/**
      * 修改操作:将index索引位置的元素修改为新元素e
      * @param index
      * @param e
      */
     public void set(int index,E e){
         if(index<0||index>=size){
             throw new IllegalArgumentException("set failed,require index>=0 && index<=size");
         }
         data[index]=e;
     }/**
      * 查询操作:获取index索引位置的元素
      * 时间复杂度为O(1)
      * @param index
      * @return
      */
     public E get(int index){
         if(index<0||index>=size){
             throw new IllegalArgumentException("get failed,require index>=0 && index<=size");
         }
         return data[index];
     }/**
      * 查找元素e所在的索引,如果不存在元素e,则返回-1
      * 时间复杂度为O(n)
      * @param e
      * @return
      */
     public int find(E e){
         for(int i=0;i<size;i++){
             if(data[i].equals(e)){
                 return i;
             }
         }
         return -1;
     }/**
      * 是否包含某一元素
      * 时间复杂度为O(n)
      * @param target
      * @return
      */
     public boolean contains(E target){
         for (E num : data) {
             if(num.equals(target))
                 return true;
         }
         return false;
     }@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();
     }
 }

6.数据结构与算法学习

https://tmdus.xetlk.com/s/eUMyr

image-20231219201126021

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懿所思

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值