0033数组结构之数组

Posted xiao1572662

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了0033数组结构之数组相关的知识,希望对你有一定的参考价值。

数组中只能存放同一种类型的数据

数组定义:1、只设定长度 2、不设定长度,但是直接给数组赋值

int[] arr = new int[10];

int[] scores = new int[]{100,99,98};

 

1、  定义数组类Array

2、  向数组中添加元素,最后位置添加和指定位置 添加、起始位置添加

3、  查询数组中的元素和修改元素

先重写toString()方法,main方法中测试该方法

4、  数组中的包含、搜索和删除指定索引、删除数组第一个元素、删除数组最后一个元素、删除指定元素(按元素值删除,之前的都是按索引位置删除)

package array;

//定义数组类
public class Array {
    //存放数组数据
   
private int[] data;
    //数组中存放的元素个数,且需要明白size总是指向的待添加元素的位置
   
private int size;

    //定义数组的容量
   
public Array(int capacity){
        data = new int[capacity];
        //初始的元素个数为0
       
size = 0;
    }

    //如果未定义容量,则使用默认容量
   
public Array(){
        this(10);
    }

    //提供方法获数组元素个数
   
public int getSize(){
        return size;
    }
    //获取数组的容量
   
public int getCapacity(){
        return data.length;
    }
    //判断数组是否为空
   
public boolean isEmpty(){
        return size==0;
    }

    //向数组末尾添加元素
   
public void addLast(int e){
        /*//如果数组元素个数等于数组长度,则不能再增加,抛异常,后期会讲解扩容
        if(size == data.length){
            throw new IllegalArgumentException("add failed! Array is full.");
        }
        data[size] = e;
        size++;*/
        //
由上边的写法改为复用add方法
       
add(size,e);
    }

    //向数组任意位置加入元素
   
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! Required index>=0 and index<=size.");
        }
        //向后移动元素、指定位置插入元素、size++
       
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);
    }

    //获取index索引位置的元素
   
public int get(int index){
        if (index<0 || index >=size){
            throw new IllegalArgumentException("get failed!index is illegal.");
        }
        return data[index];
    }

    //更改索引位置的元素为指定元素
   
public 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;
    }
    //查找元素在数组中的位置
   
public int find(int e){
        for(int i=0;i<size;i++){
            if(data[i] == e){
                return i;
            }
        }
        //如果元素在数组中不存在,返回-1
       
return -1;
    }

    //删除指定索引位置的元素,并返回该元素值
   
public int remove(int index){
        //判断索引位置是否合法、保存删除位置的元素值做为返回值,元素前移、size--、返回元素
       
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);
    }

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

    //根据元素值删除数组中的元素
   
public void removeElement(int 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 ",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();
    }
}

 

package array;

public class Main {
    public static void main(String[] args) {
        //测试自定义Array类的addLasttoString方法
       
Array arr = new Array(20);
        for(int i=0;i<10;i++){
            arr.addLast(i);
        }
        System.out.println(arr.toString());
        //测试add方法
       
arr.add(1,200);
        System.out.println(arr.toString());
        //测试addFirst方法
       
arr.addFirst(-1);
        System.out.println(arr.toString());

        boolean flag = arr.contains(8);
        System.out.println("测试是否包含8"+flag);
        arr.remove(5);
        System.out.println(arr.toString());
        arr.removeFirst();
        System.out.println(arr.toString());
        arr.removeLast();
        System.out.println(arr.toString());
        arr.removeElement(200);
        System.out.println(arr.toString());

    }
}

 

 

5、  泛型在数组中的应用

以上设计的数组最大的问题,就是只能存放int类型的数据,而实际中数组做为容器应该能够存放任意类型的数据

改为泛型时需要注意事项:

1)  创建数组对象的方式

2)  contains()、find等方法中做比较的时候,不应该再用==引用比较,而应该使用equals进行值比较

3)  删除指定位置元素时,由于后边的元素前移,且size—后,最后size所指向的位置,实际上还保留着之前的最后一个元素,导致无法进行垃圾回收,应该手动将该值设置为null;

改后的Array类编码如下:package array;


//定义数组类
public class Array<E> {
    //存放数组数据
   
private E[] data;
    //数组中存放的元素个数,且需要明白size总是指向的待添加元素的位置
   
private int size;

    //定义数组的容量
   
public Array(int capacity){
        //data = new E[capacity]; 这种写法语法上是不对的,应该写成如下写法
       
data = (E[])new Object[capacity];
        //初始的元素个数为0
       
size = 0;
    }

    //如果未定义容量,则使用默认容量
   
public Array(){
        this(10);
    }

    //提供方法获数组元素个数
   
public int getSize(){
        return size;
    }
    //获取数组的容量
   
public int getCapacity(){
        return data.length;
    }
    //判断数组是否为空
   
public boolean isEmpty(){
        return size==0;
    }

    //向数组末尾添加元素
   
public void addLast(E e){
        /*//如果数组元素个数等于数组长度,则不能再增加,抛异常,后期会讲解扩容
        if(size == data.length){
            throw new IllegalArgumentException("add failed! Array is full.");
        }
        data[size] = e;
        size++;*/
        //
由上边的写法改为复用add方法
       
add(size,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! Required index>=0 and index<=size.");
        }
        //向后移动元素、指定位置插入元素、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);
    }

    //获取index索引位置的元素
   
public E get(int index){
        if (index<0 || index >=size){
            throw new IllegalArgumentException("get failed!index is illegal.");
        }
        return data[index];
    }
  //获取最后一个元素
  public E getLast(){
   return get(size-1);
  }

  //获取第一个元素
  public E getFirst(){
   return get(0);
  }
    //更改索引位置的元素为指定元素
   
public void set(int index,E e){
        if (index<0 || index >=size){
            throw new IllegalArgumentException("get failed!index is illegal.");
        }
        data[index] = e;
    }

    //判断数组中是否包含指定元素
   
public boolean contains(E e){
        for(int i=0;i<size;i++){
            if(data[i].equals(e)){
                return true;
            }
        }
        return false;
    }
    //查找元素在数组中的位置
   
public int find(E e){
        for(int i=0;i<size;i++){
            if(data[i].equals(e)){
                return i;
            }
        }
        //如果元素在数组中不存在,返回-1
       
return -1;
    }

    //删除指定索引位置的元素,并返回该元素值
   
public E remove(int index){
        //判断索引位置是否合法、保存删除位置的元素值做为返回值,元素前移、size--、返回元素
       
if (index<0 || index >=size){
            throw new IllegalArgumentException("remove failed!index is illegal.");
        }
        E ret = data[index];
        for(int i=index+1;i<size;i++){
            data[i-1] = data[i];
        }
        size--;
        data[size] = null;
        return ret;
    }

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

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

    //根据元素值删除数组中的元素
   
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 ",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();
    }

}

 

4)  在测试的时候,即在使用Array类创建对象的时候,需要用<>指定存放的具体数据类型,其他内容都不用动

//也可以写成Array<Integer> arr = new Array(20);
Array<Integer> arr = new Array<Integer>(20);

 

5)  编写自定义类,并测试Array类中存储自定义类

package array;

public class Employee {
    private String name;
    private int age;

    public Employee(String name,int age){
        this.name=name;
        this.age=age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name=‘" + name + +
                ", age=" + age +
                ‘}‘;
    }

    public static void main(String[] args) {
        //不指定长度,Array类中定义的默认10
       
Array<Employee> emps = new Array<>();
        emps.addLast(new Employee("zhangsan",18));
        emps.addLast(new Employee("lisi",20));
        emps.addLast(new Employee("wangwu",35));
        System.out.println(emps);
    }
}

 

6、  动态数组

以上定义的代码使用的是静态数组,而我们实际工作中,有些时候是不能预估数组需要存放数据的多少的,因此使用动态数组解决该问题

1)  定义私有的扩容方法resize(int newCapacity)

思想:定义新的数组,将旧数组的内容循环遍历拷贝进新的数组,原数组的声明变量指向新的数组

2)  新增元素和删除元素的方法中增加判断的方法,新增时当元素个数达到数组的长度(容量)时,调用resize方法进行扩容;删除元素时,当元素个数为数组容量的一半时,调用resize方法进行缩容

精髓就是定义新的数组,使原有数组指向新的数组,同时会放弃原有数组的引用

resize方法如下:

//数组动态扩容
public void resize(int newCapacity){
    E[] newData = (E[])new Object[newCapacity];
    for(int i=0;i<size;i++){
        newData[i] = data[i];
    }
    //使data数组指向新的引用
   
data = newData;
}

add方法如下:

//向数组任意位置加入元素
public void add(int index,E e){
    //判断欲插入的元素的下标位置是否正确
   
if(index<0 || index > size){
        throw new IllegalArgumentException("add failed! Required index>=0 and index<=size.");
    }
    //如果数组元素个数等于数组长度,则进行动态扩容,但是扩容涉及到数组中元素的循环复制,要考虑效率
   
if(size == data.length){
        resize(2 * data.length);
    }

    //向后移动元素、指定位置插入元素、size++
   
for(int i=size-1;i>=index;i--){
        data[i+1]=data[i];
    }
    data[index] = e;
    size++;
}

remove方法如下:

//删除指定索引位置的元素,并返回该元素值
public E remove(int index){
    //判断索引位置是否合法、保存删除位置的元素值做为返回值,元素前移、size--、返回元素
   
if (index<0 || index >=size){
        throw new IllegalArgumentException("remove failed!index is illegal.");
    }
    E ret = data[index];
    for(int i=index+1;i<size;i++){
        data[i-1] = data[i];
    }
    size--;
    data[size] = null;
    //当元素个数为数组容量的一半时进行动态缩容
   
if(size == data.length/2){
        resize(data.length/2);
    }
    return ret;
}

 

 

7、  时间复杂度分析

O(1)、O(n)、O(lgn)、O(nlogn)、O(n^2)

用大O描述的是算法的运行时间和输入数据之间的关系

为什么要用大O,叫做O(n):忽略常数,实际时间T=c1*n+c2

如T=2*n+3 和T=10000*n + 20000时间复杂度都是O(n),都是线性时间的算法

T=2*n*n+3 和T=1*n*n +300*n +20的时间复杂度都是O(n^2)

addLast(e): O(1)

addFirst(e):O(n)

add(index,e):O(n/2)=O(n),1/2是常数,所以O(n/2)=O(n)

所以添加添加整体上是O(n)的时间复杂度,即算法分析,我们主要关心的是最坏的情况下的时间复杂度。

删除操作时间复杂度O(n)

修改操作时间负责度O(1)

查找:get(index)时间复杂度O(1),contains(e)时间复杂度O(n),find(e)时间复杂度O(n)

总结

增加时间复杂度O(n),

删除时间复杂度O(n),

修改:已知索引时间复杂度O(1),未知索引时间复杂度O(n)

查询:已知索引时间复杂度O(1),未知索引时间复杂度O(n)

 

8、  均摊复杂度和防止复杂度震荡

增加最后一个元素和删除最后一个元素,简单看是O(1)的,但是如果考虑到resize,而resize是需要复制一个数组的元素给另外一个数组的,所以是O(n)级别的,但是这种情况使用最坏的时间复杂度分析是不合理的,因为必须满足扩容或者缩容的情况下才会进行resize,这种概率是极低的,不应该用这种分析,而是用均摊时间复杂度,n次操作导致一次resize,而一次resize会移动n次并插入1次,相当于插入n次共做了2n+1次基本操作,相当于做一次插入需要2次的基本操作,所以addLast(e)与数组中元素的个数n并无关系,所以实际上还是O(1)时间复杂度的。

复杂度震荡:达到扩容的条件,进行添加,会进行扩容,而添加完又进行删除,此时会进行缩容,即在扩容缩容临界点反复进行增加和删除,每次都会的时间复杂度都是O(n)

解决办法:不要过于着急的缩容,即不是在数组中元素个数为容量的1/2时就进行缩容,而是等到数组中元素个数为容量的1/4时再进行缩容,且缩容为数组容量的1/2,留出空闲空间给新增元素时使用,防止新增就扩容

         修改remove(index)方法中缩容部分的代码为如下:

 //当元素个数为数组容量的一半时进行动态缩容
/*if(size == data.length/2){
    resize(data.length/2);
}*/
//
由元素个数为1/2容量时就进行缩容,改为元素个数为1/4容量时才进行缩容,
//且需要保证1/2容量时数组容量不等于0,因为创建数组的长度是不能为0
if(size == data.length/4 && data.length/2!=0){
    resize(data.length/2);
}

如有理解不到之处,望沟通指正!

以上是关于0033数组结构之数组的主要内容,如果未能解决你的问题,请参考以下文章

20160223.CCPP体系具体解释(0033天)

从零开始学Go之容器:切片

VSCode自定义代码片段—— 数组的响应式方法

VSCode自定义代码片段10—— 数组的响应式方法

JDK常用数据结构

数据结构之数组