数据结构之【数组】

1 数据结构之数组

数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同。Java 语言中提供的数组是用来存储固定大小的同类型元素。你可以声明一个数组变量,如 numbers[100] 来代替直接声明 100 个独立变量 number0,number1,....,number99。数组一旦声明完毕,长度就固定了。

数组的声明、创建和初始化。

package cc.myall.demo01;
import org.junit.Test;
public class demo01 {

    @Test
    public void test01(){
        int[] array = new int[10];
        int len = array.length;
        for(int i=0; i < len; i++ ){
            System.out.println(array[i]);  //默认值是0
        }  
    }
}

运行的结果就是打印出10个0。

数组最大的优点就是查询快,使用索引来进行查询;

数组的索引最好是有语义的。

1.1 二次封装自己的数组

基于java数组来二次封装自己的数组类(Array),使之成为动态数组,也就是java内置的ArrayList类。下面来自己实现一下。

size指向的是第一个没有元素的位置。

新建一个类Array,

package cc.myall.demo01;
public class Array {
    private int[] data; //先假定这个数组只能装int类型
    private int size;
    
    //构造函数,传入数组的容量来构造数组
    public Array(int capacity) {
        data = new int[capacity];
        size = 0;  //实际大小初始为0
    }
    public Array() {
        this(10);
    }
    //获取数组中元素的个数
    public int getSize() {
        return size;
    }
    
    // 获取数组的容量
    public int getCapacity() {
        return data.length;
    }
    
    //判断数组是否为空
    public boolean isEmpty() {
        return size == 0;
    }
}

测试:

package cc.myall.demo01;
import org.junit.Test;
public class demo01 {    
    @Test
    public void test02(){
        Array arr = new Array();
        System.out.println(arr.getCapacity());
    }
}

运行结果:10

修改为支持泛型

public class Array<E> {
    private E[] data; //先假定这个数组只能装int类型
    private int size;
    
    //构造函数,传入数组的容量来构造数组
    public Array(int capacity) {
        data = (E[]) new Object[capacity];
        size = 0;  //实际大小初始为0
    }
    public Array() {
        this(10);
    }
    //获取数组中元素的个数
    public int getSize() {
        return size;
    }
    
    // 获取数组的容量
    public int getCapacity() {
        return data.length;
    }
    
    //判断数组是否为空
    public boolean isEmpty() {
        return size == 0;
    }
}

1.2 使数组具有增删查改功能

addLast(int e) 时间复杂度O(1)

在最后一个元素的后面添加一个元素,原理就是在size处添加,然后让size加1。在Array类中添加addLast函数。

//在最后一个元素的后面添加元素
    public void addLast(E e) {
        if(size == data.length) {
            throw new IllegalArgumentException("添加失败,数组已满");
        }
        data[size] = e;
        size ++;
    }

add(int index, int e) 时间复杂度O(n)

在元素中间的某个位置插入元素,让改位置及以后的元素一次移动一个位置,即size-1位置的一道size处,以此类推,移动后如下图,就腾出一个位置来:

这里说的腾出一个位置并不是说改位置为空,而是可以插入覆盖。代码如下

    //向指定index处添加元素
    public void add(int index, E e) {
        if(size == data.length) {
            throw new IllegalArgumentException("添加失败,数组已满");
        }
        if(index < 0 || index > size) {
            throw new IllegalArgumentException("添加失败,要求0<=index<=size");
        }
        for(int i = size-1; i >= index; i --) {
            data[i+1] = data[i];
        }
        data[index] = e;
        size ++;
    }

再看addLast(int e) 时间复杂度O(1)

可以利用add函数改写,此时的index就是size。

    //在最后一个元素的后面添加元素

    public void addLast(E e) {

        add(size, e);

    }

addFirst(int e) 时间复杂度O(n)

可以利用add函数改写,此时的index就是0。在数组的第一个元素出插入元素

    // 在数组的第一个位置添加元素

    public void addFirst(E e) {

        add(0, e);

    }

get set时间复杂度O(1)

    // 获取index索引处的元素
    public E get(int index) {
        if(index < 0 || index > size) {
            throw new IllegalArgumentException("添加失败,要求0<=index<=size");
        }
        return data[index];
    }

   

    // 改变index索引处的元素
    public void set(int index, E e) {
        if(index < 0 || index > size) {
            throw new IllegalArgumentException("添加失败,要求0<=index<=size");
        }
        data[index] = e;
    }

contains()时间复杂度O(n)

查找数组中是否包含元素e

    //查找数组中是否有元素e
    public boolean contains(E e) {
        for(int i = 0; i < size; i++) {
            if(data[i].equals(e)) {
                return true;
            }
        }
        return false;
    }

find() 时间复杂度O(n)

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

remove() 时间复杂度O(n)

删除指定位置的元素,并且返回删除的元素。原理和添加一个元素类似。

    

// 删除指定位置index的元素

    public E remove(int index) {
        if(index < 0 || index > size) {
            throw new IllegalArgumentException("添加失败,要求0<=index<=size");
        }
        E ret = data[index];
        for(int i = index + 1; i < size; i ++) {
            data[i-1] = data[i];
        }
        size --;
        return ret;
    }

1.3 动态数组的实现

在往数组中添加元素的时候,如果数组已经满了,可以重新建一个新的较大容量的数组,把原来的数组的元素全部放入新数组中,这样就可以实现扩容。减少元素到一定程度就缩容。

   

 //向指定index处添加元素
    public void add(int index, E e) {
        if(index < 0 || index > size) {
            throw new IllegalArgumentException("添加失败,要求0<=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 ++;
    }

  

  // 删除指定位置index的元素
    public E remove(int index) {
        if(index < 0 || index > size) {
            throw new IllegalArgumentException("添加失败,要求0<=index<=size");
        }
        E 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;
    }

 

时间复杂度O(n)

    private void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity];
        for(int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

1.4 均摊时间复杂度和防止复杂度震荡

实现动态数组之后,addLast操作的时间复杂度最坏是O(n)的。

假设数组容量是n,n+1次操作触发一次扩容操作,总共2n+1次操作。平均每次addLast操作,进行两次基本操作。这样均摊计算,addLast操作的时间复杂度是O(1),在这个例子里,均摊计算比最坏计算要有意义。

复杂度振荡产生:当addLast一个元素需要扩容时,复杂度是O(n),然后又马上删除一个元素,要缩容,时间复杂度也是O(n)。均摊的计算方法就不能用了,产生震荡。

解决办法:让数据元素个数减少到容积的1/4时再缩容,仍缩为原来的一半。

 

   // 删除指定位置index的元素
    public E remove(int index) {
        if(index < 0 || index > size) {
            throw new IllegalArgumentException("添加失败,要求0<=index<=size");
        }
        E 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) { // Lazy方式
            resize(data.length / 2);
        }
        return ret;
    }

代码: https://github.com/zhang-anan/DataStructure/tree/master/src/cc/myall/demo01

 

原文地址:https://www.cnblogs.com/zhang-anan/p/10086440.html