java 数组

这是这段时间学习数组的一个笔记和总结。

数组概述:

声明一个数组就是在内存空间中划出一串连续的空间,数据具有以下特点:
1. 数组名代表的是连续空间的首地址
2. 通过首地址可以依次访问数组所有元素
3. 元素在数组中的排序叫做下标从零开始
4. 数组可以看成是多个相同数据类型数据的组合,对这些数据的统一管理。
5. 数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。
6. 数组中的元素可以是任何类型,包括基本类型和引用类型。

优点:

1. 通过下标访问元素的效率很高,指定下标为n的元素的地址:首地址*元素类型字节数
2. 数组可以保存若干个元素的值。

缺点:

1. 数组长度是固定的不能变的;
2. 数组进行元素的删除和插入操作的时候,效率比较低。需要移动大量的元素
3. 数组元素的类型只能是一种
4. 数组通过内容查找元素的效率比较低的。
5. 数组的元素是连续分配的,所以在heap内存中必须找到连续的内存空间才能容纳数组的所有数据。对内存要求高一些;
6. 数组没有提供任何的封装,所有对元素的操作,都是通过自定义的方法实现的,对数组元素的操作比较麻烦;

一维数组的声明:

1. 一维数组的声明方式:
类型 数组名称[]
类型[] 变量名称;
例如:

int arr[];
int[] arr;


2. java语言中声明数组时不能指定其长度(数组中元素的个数),例如:

int arr[5]; // 会提示编译不通过

数组对象的创建:

1. java中使用关键字new 创建数组对象,格式为:
数组名 = new 数组元素类型[数组元素个数];
数组创建:

/**
 * 数组对象的创建
 */
private static void define() {
    int arr[];
    arr = new int[10];
    for(int i = 0; i < 10; i ++){
        arr[i] = i;
        System.out.println("===============> arr[" + i + "]" + arr[i]);
    }
}

 数组初始化:

1. 静态初始化
在定义数组的同时就为数组元素分配空间并赋值,例如:

private static void init1() {
    int arr[] = {1, 2, 3, 4, 5};
    for(int i = 0; i < arr.length; i ++){
        arr[i] = i;
        System.out.println("===============> arr[" + i + "]" + arr[i]);
    }
}

2. 动态初始化:

数组定义与为数组元素分配空间和赋值的操作分开进行,例如:

private static void init1() {
  int[] arr = new int[5];
    arr[0] = 1;
    arr[1] = 2;
    arr[2] = 3;
    for(int i = 0; i < arr.length; i ++){
        arr[i] = i;
        System.out.println("===============> arr[" + i + "]: " + arr[i]);
    }
}

3. 数组元素的默认初始化:

数组是引用类型,它的元素相当于类的成员变量,因此数组分配空间后,每个元素也被按照成员变量的规则被隐式初始化,例如:

/**
 * 数组初始化
 */
private static void init() {
    int[] arr = new int[5];
    User[] user = new User[5];
    System.out.println(arr[1]);
    System.out.println(user[1]);
}

user:

class User {

    String name;
    String sex;

    public User(String name, String sex){
        this.name = name;
        this.sex = sex;
    }
}

输出结果:

0
null

可以看到每个元素也被按照成员变量的规则被隐式初始化

数组元素的引用:
1. 定义并用运算符new为之分配空间后,才可以引用数组中的每个元素,数组元素的引用方式为:
数组名[index]
index为数组元素下标,可以使整形常亮或整形表达式。如:
arr[3],  arr[i];
数组元素的下标从0开始;长度为n的数组的合法下标取值范围为:0~n-1;

2. 每个数组都有一个属性lendth(注:这里length是一个属性,不是方法,没有加括号(),我们这里特别说明是为了和String的length()方法做区别)指明他的长度,例如:
a.length的值为数组a的长度(元素个数)
我们每个类中的主函数也有一个数组,名叫args,

public static void main(String args[]){
}

这个参数可以接受命令行参数,我们在输入
java类名 xx,xxx  

基础类型的包装类
基础类型的包转类, 基础类型是分配在栈内存中的,包装类是分配在堆空间里面的 。
基础类型的包装类有:Boolean---boolean、Byte---byte、Character---char、Double---double 、 
Float---float、Integer---int、Long---long、Short---short。 

 二维数组:
1、二维数组可以看成是以数组为元素的数组。例如:

  int arr[][] = {{1, 2}, {3, 4}};

2、java中多维数组的声明和初始化应按从高维到低维的顺序进行,例如:

int arr[][] = new int[3][];
arr[0] = new int[2];
arr[1] = new int[4];
arr[2] = new int[3];

二维数组初始化:
1、静态初始化:

  int arr[][] = {{1, 2}, {3, 4}};

2、动态初始化:

int arr[][] = new int[3][];
arr[0] = new int[2];
arr[1] = new int[4];
arr[2] = new int[3];

数组的拷贝:
1、使用java.lang.system类的静态方法

public static void arrayCopy(object src, int srcPos, object dest, int destPos, intlength){
    
}

2、可以用于数组src从第srcPos项元素开始的length个元素拷贝到目标数组从destPos项开始的lenght个元素。
3、如果源数据数目超过目标数组边界会抛出IndexOutOfBoundsException异常。
例子:

private static void copy() {
    int[] a = {1, 2, 3};
    int[] b = new int[5];
    System.arraycopy(a,0, b,0, a.length);
    for(int i = 0; i < b.length; i ++){
        System.out.println("===============> b[" + i + "]: " + b[i]);
    }
}

数组的封装:
下面是对一个数组的基本封装,提供下面一些实现
getSize() 获取数组的容量 
isEmpty() 返回数组是否为空
add(int index, E e) 在index索引的位置插入一个新元素e
addLast(E e) 向所有元素后添加一个新元素
addFirst(E e) 在所有元素前添加一个新元素
get(int index) 获取index索引位置的元素
set(int index, E e) 修改index索引位置的元素为e
contains(E e) 查找数组中是否有元素e
find(E e) 查找数组中元素e所在的索引,如果不存在元素e,则返回-1
remove(int index) 从数组中删除index位置的元素, 返回删除的元素
removeFirst() 从数组中删除第一个元素, 返回删除的元素
removeLast() 从数组中删除最后一个元素, 返回删除的元素
removeElement(E e) 从数组中删除元素e
代码:

public class Array<E> {

    private E[] data;
    private int size;

    // 构造函数,传入数组的容量capacity构造Array
    public Array(int capacity){
        data = (E[])new Object[capacity];
        size = 0;
    }

    // 无参数的构造函数,默认数组的容量capacity=10
    public Array(){
        this(10);
    }

    // 获取数组的容量
    public int getCapacity(){
        return data.length;
    }

    // 获取数组中的元素个数
    public int getSize(){
        return size;
    }

    // 返回数组是否为空
    public boolean isEmpty(){
        return size == 0;
    }

    // 在index索引的位置插入一个新元素e
    public void add(int index, E e){

        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Require index >= 0 and 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 ++;
    }

    // 向所有元素后添加一个新元素
    public void addLast(E e){
        add(size, e);
    }

    // 在所有元素前添加一个新元素
    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];
    }

    // 修改index索引位置的元素为e
    public void set(int index, E e){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Set failed. Index is illegal.");
        data[index] = e;
    }

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

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

    // 从数组中删除index位置的元素, 返回删除的元素
    public E remove(int index){
        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; // loitering objects != memory leak

        if(size == data.length / 4 && data.length / 2 != 0)
            resize(data.length / 2);
        return ret;
    }

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

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

    // 从数组中删除元素e
    public void removeElement(E e){
        int index = find(e);
        if(index != -1)
            remove(index);
    }

    @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();
    }

    // 将数组空间的容量变成newCapacity大小
    private void resize(int newCapacity){

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

    // test
    public static void main(String[] args) {

        Array<Integer> arr = new Array<>();
        for(int i = 0 ; i < 10 ; i ++)
            arr.addLast(i);
        System.out.println(arr);

        arr.add(1, 100);
        System.out.println(arr);

        arr.addFirst(-1);
        System.out.println(arr);

        arr.remove(2);
        System.out.println(arr);

        arr.removeElement(4);
        System.out.println(arr);

        arr.removeFirst();
        System.out.println(arr);

        for(int i = 0 ; i < 4 ; i ++){
            arr.removeFirst();
            System.out.println(arr);
        }
    }
}

简单复杂度分析:

我们平时经常看到的一些时间复杂度:
O(1), O(n), O(lgn), O(nlogn), O(n^2)
上面的O描述的是算法的运行时间和输入数据之间的关系,这个并不是O严格的数学定义。
看下面一个例子,n表示的是nums中的元素个数,那么O(n)表示下面的的算法运行时间的多少和nums元素的个数成线性关系的,n表示的幂是一次方,所以说是线性关系。
public static int sum(int[] nums) {
    int sum;
    for(int num : nums)
        sum += num;
    return num;
}
上面的场景忽略和很多常数的情景,实际上线性的关系严格可以表示为:
T=c1 * n + c2
上述方程由于在不同环境,不同语言等差异,很难确定c1和c2的值,所以我们在分析时间复杂度的时候往往忽略掉常数进行分析,表示算法消耗的时间和输入数据的规模的关系。O(n)描述的是渐进时间复杂度,描述的是n趋于无穷的情况,所以在数量少的时候,O(n)并不一定就快于o(n^20),还和常数相关,但是在n趋于无穷大的时候,低阶算法的性能是快于高阶算法的。当一个算法既有高阶项又有低阶项的时候,往往只考虑高阶项,低阶项将被忽略掉,因为当n趋于无穷的时候,低阶项起的作用就不大了。

上述例子的时间复杂度分析:

添加操作:
addLast(E e) O(1) 表示这个操作和数组中的数据规模是没有关系的。
addFirst(e) O(n)
add(index, e) O(n/2) = O(n)
上面的方法可以引入概率分析,因为插入的index概率是一样的,上面的添加操作综合考虑可以说是一个O(n)的算法。在时间复杂度分析上,我们通常考虑的是最坏的情况。
删除操作:
removeLast(e) O(1)
removeFirst(e) O(n)
remove(index, e) O(n/2) = O(n)
综合来看,删除操作的时间复杂度是O(n)

修改操作:
set(index, e) O(1)

查找操作
get (index) O(1)
contains(e) O(n)
find(e) O(n)

均摊复杂度分析:

在上面的例子中,我们使用addLast(e)添加元素时,有可能会调用resize方法实现数组的扩容,所以这个方法的时间复杂度是O(n)。但是我们忽略了一种情况,就是并不是每次addLast都会导致数组扩容,我们使用最坏情况分析是不合理的。实际上我们每次扩容都是基于现有容量的,假设addLast()现在导致扩容操作,那么总共操作的次数是:现有容量 * 2 + 1(当前操作),平均来说,相当于每次addLast操作,都进行2次基本操作,相当于把总共addLst的时间平摊给了每个操作。这样均摊分析的话,时间复杂度是O(1),这种算法称为均摊复杂度分析。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值