【数据结构与算法】重新认识一下数组

 

package com.lkj;

public class Array<E>
{
    private E[] data;//数组容量capacity就是数组的length:data.length
    private int size;//数组中有效元素的个数

    //初始化数组的构造函数
    public Array(int capacity)
    {
        //初始化数组容量为capacity,数组中有效元素个数为0
//        data = new E[capacity];
        /**
         * 注意,java不支持直接:new E[capacity] 这样new一个E泛型类型的数组。
         * 我们必须先new一个Object类型的数组,然后再将其转换为E类型的数组
         */
        data = (E[])new Object[capacity];//注意,是转换为E类型的数组:E[] , 而不是转换为E类型
        size = 0;
    }

    // 无参数的构造函数,如果用户不指定,默认数组的容量capacity=10
    public Array()
    {
        //调用上面的有参构造函数初始化
        this(10);
    }

//--------------------------------------------------------------------------------- 获取数组容量,元素个数,判断数组是否为空
    // 获取数组中的元素个数
    public int getSize()
    {
        return size;
    }

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

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

//---------------------------------------------------------------------------------------添加元素

    /**
     * add()、addFirst()方法时间复杂度为O(n),addLast()时间复杂度为O(1);
     * 综合来说,数组添加操作是O(n)级别的算法。——我们通常按照最坏的情况来计算时间复杂度。
     *
     * 扩容的时间复杂度是O(n)——原先有多少个元素就要赋值多少次。
     */

    //向所有元素后添加一个新元素(既在数组的末尾添加一个新的元素)
    public void addLast(E e)
    {
//        //添加元素之前判断数组元素个数是否已经等于数组容量,等于则不可再添加(既数组已经放满了)
//        if(size == data.length)
//        {
//            throw new IllegalArgumentException("AddLast fail . Array is full.");
//        }
//        data[size] = e;
//        size++;

        //addLast方法可以复用add方法,add方法自然会检查数组是否放满或者插入位置是否不正确
        add(size , e);//向末尾添加元素,既在size位置添加
    }

    // 在所有元素前添加一个新元素(既在数组的0角标添加元素)
    public void addFirst(E e)
    {
        add(0,e);
    }

    // 在index索引的位置插入一个新元素e
    public void add(int index , E e)
    {
        //首先同样判断数组是否已经放满
        if(size == data.length)
        {
//            throw new IllegalArgumentException("Add fail . Array is full.");
            /**
             * 当数组元素放满之后,我们不抛出异常,而是进行扩容,将整个数组的容量扩容2倍。
             * 其实就是生成一个新的数组,将原来数组的元素存储到新数组中,并将原来数组的引用指向新的数组。
             * 原先数组的地址没有引用指向,就会被JVM自动垃圾回收.
             *
             * 这样操作后,我们的数组从可以根据需求自动扩容
             */
            resize(2*size);//使用resize函数实现扩容2倍,这样就可以继续添加元素。
        }

        //其次,判断index插入的位置是否不合法:index<0或者index>size ,插入的位置不合法(可以插入size位置,既插入数组当前元素的后边)
        if(index<0 || index>size)
        {
            throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size.");
        }

        //最后,将index索引之后的元素向后移动一位。
        for(int i=size-1 ; i>=index ; i--)
        {//将数据后移一位。其实只是将i位置的元素复制一份放到i+1,而i的元素还存在,但是这个时候我们可以覆盖这个元素
            data[i+1] = data[i];
        }
        /*
        如果index=size,那么不会进入循环,直接将size位置的数据设置为e
         */
        data[index] = e;//数据移动完毕后,将索引为index的位置的数据设置
        size++;//最后,数组长度记得加1
    }

//-----------------------------------------------------------------------------------------获取、设置元素

    /**
     * 如果知道元素的索引:
     * set(int index) 修改操作的时间复杂度是O(1)——数组支持随机访问
     * get(int index) 获取操作的时间复杂度是O(1),
     *
     * 如果不知道要删除元素的索引,通过contains(e)方法判断数组是否存在这个元素,这个方法时间复杂度为O(n),
     * 再通过find(e)方法获取数组的元素的索引,这个方法的时间复杂度为O(n)
     */

    // 获取index索引位置的元素
    public E get(int index)
    {
        //判断输入的索引是否正确
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Get failed. Index is illegal.");

        //这里相当于对data数组进行隐藏,用户只能通过get方法来获取data这个静态数组某一个索引对应的元素,而不能直接获得data数组(data设置为private,用户无法获取data)
        //这样做的好处:可以对用户传入的索引进行判断,避免索引错误。比如数组中有的空间没有存储元素,那么通过get方法用户无法查询data数组没有使用的空间。
        //如果直接使用data[索引]的方式,有可能会出现用户输入索引错误的问题。
        return data[index];
    }

    //获取数组最后一个元素
    public E getLast()
    {
        //我们这里不使用data[size-1],这是因为size可能等于0,使用data[size-1]可能造成索引为负数。
        //而使用get(size-1),这个方法或判断索引是否合法,不合法会抛出异常
        return get(size-1);
    }

    //获取数组第一个元素
    public E getFirst()
    {//同样,如果数组为null,data[0]会出错,get(0)则会判断索引是否合法
        return get(0);
    }

    // 修改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;
    }

//----------------------------------------------------------------------------------------查找元素

    /**
     * contains(e) 方法时间复杂度为O(n),
     * find(e)方法的时间复杂度为O(n)
     */

    // 查找数组中是否有元素e
    public boolean contains(E e)
    {
        for (int i = 0; i < size; i++)
        {
            //e是类类型,如果要进行值的比较,应该使用equals,使用==比较的是地址
            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;
    }


//----------------------------------------------------------------------------------------删除元素

    /**
     * remove()、removeFirst()方法时间复杂度为O(n),removeLast()时间复杂度为O(1);
     * 综合来说,数组删除操作是O(n)级别的算法。——我们通常按照最坏的情况来计算时间复杂度。
     *
     * 缩容的时间复杂度是O(n)——缩容同样要将原来数组的值赋予新的数组
     */

    // 从数组中删除index位置的元素, 返回删除的元素
    public E remove(int index)
    {
        //首先,判断要删除的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 -1 ; i++)
        {
            data[i-1] = data[i];
        }
        //如果要删除的index是最后一个size-1,不会进入循环,而是直接将size-1,将最后一个元素删除
        size--;
        /**
         * 我们在删除的时候,其实size位置的元素还存在,只不过我们无法访问到size位置的元素。
         * 当我们的元素是类类型的时候,数组存储的是类类型对象的引用的,删除后data[size]还指向引用的位置,此时jvm的垃圾回收机制不会自动将其回收。
         * 因此,我们在删除后将size位置的元素置为null,这样删除后size位置的引用就会被JVM回收
         */
        data[size] = null;//loitering objects != memory leak ,lortering objexts不等于内存泄漏


        /**
         * 当我们的数组删除到使得元素个数等于原来数组容量的一半的时候,我们需要简化数组容量缩小到原来的一半。
         * 当我们减少元素到一定程度后,数组空间自动缩小,节省内存。
         *
         * 这样操作后,我们的数组从可以根据需求自动缩小容量,以节省空间
         */
//        if(size == data.length/2)
        //采用Lazy的策略,等到元素个数为数组容量1/4的时候再进行缩容,缩容为原来的一半,防止add胡remove的时候出现时间复杂度的震荡
        if(size == data.length/4 && data.length/2 != 0)
        {
            /**
             * 当size=0,此时data.length<4,有可能data.length=1,data.length/2可能等于0,
             * (比如初始定义data.length=1,size=1,减少一个size=0=data.length/4,此时data.length/2=0)
             * 但是我们在new一个数组空间的时候,不能new一个长度为0的数组,必须再添加一个条件:data.length/2 != 0,
             * 当数组容量为1的时候,不再进行缩容。
             */
            resize(data.length/2);
        }

        return ret;
    }

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

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

    // 从数组中删除元素e——我们当前的数组可以存储重复的元素,这个removeElement方法只删除了数组红最前面的一个e元素
    public void removeElement(E e)
    {
        //首先通过find方法找到元素e的索引
        int index = find(e);
        //如果返回的index不是-1,说明数组中存在元素e
        if(index != -1)//必须先判断数组中存在元素e
        {
            //调用remove方法删除这个元素
            remove(index);
        }
    }

//----------------------------------------------------------------------------------------toString方法

    //重写数组的toString方法
    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        /*
        format(String format, Object... args) ,静态方法,使用指定的格式字符串和参数返回一个格式化字符串。
        添加这个字符串来反应数组的容量,元素个数等基本信息
         */
        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();
    }

//-----------------------------------------------------------------------------------------扩容数组

    /**
     * 改变的时间复杂度是O(n)——原先数组有多少个元素就要赋值多少次。
     */

    // 将数组空间的容量变成newCapacity大小(注意,这个方法是private的,用户无法调用,只有需要扩容的时候系统才能调用)
    private void resize(int newCapacity)
    {
        //创建一个E类型的,容量是newCapacity的新的数组
        E[] newData = (E[])new Object[newCapacity];//同样,无法直接创建E类型的数组,只能此案创建Object类型的数组,再将其转换为E[]类型

        //将原来data数组的元素存储到newData数组
        for (int i = 0; i < size ; i++)
        {
            newData[i] = data[i];
        }
        //最后,将原来data数组数组的引用指向新数组newData的地址。
        //这样,我们原来的data就指向新的地址,且数组的容量扩充2倍,这样添加元素的时候就可以向里面添加元素。
        data = newData;

        /**
         * 使用resize方法,我们的数组从静态数组(容量固定),转变为动态数组(可以根据需求自动扩容或者缩小)。
         * 这样用户便不需要考虑内存大小问题,直接创建一个默认数组往里面存储数据,当空间不够的时候数组会自动扩容,当空间过多的时候内存会自动缩容
         */
    }

}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值