【数据结构】动态数组的实现

目录

🌟前言

🌟定义一个线性表接口SeqList

🌟SeqList接口的实现类:MyArray

🌈 初始化

🌈 元素尾插

🌈 自定义实现grow扩容方法     

🌈 在索引位置添加元素(重点)

🌈 删除索引位置元素

🌈 删除第一个值为element的元素

🌈 删除所有值为element的元素(重点)

🌈改:修改index位置下的元素,并返回被修改的原值

🌈查:查询索引index位置下的元素

🌈 判断数组是否包含element元素值,返回判断结果。

🌈 返回元素element在数组中对应的索引

🌈 重写ToString方法

🌟完整代码实现

🌟测试类

🌟总结 


🌟前言

        需要知道: 我们平时说的数组,最大的特点就是定长,它的长度是固定的。而我们今天说的冬天数组与其最大的区别就在于:动态数组的长度并不是固定的。它本质上就是在基本数组的基础上封装到了类中,至于数组的长度则由类的内部来进行扩容操作。对于用户来说,使用动态数组不需要关心数组的长度。知道这一点即可。接下来我们就来自己实现一下动态数组~(元素的添加和删除是重点)

🌟定义一个线性表接口SeqList

public interface SeqList<E> {
    /**
     * 增:直接增加元素
     */
    void add(E element);
    /**
     * 增:在索引为Index的位置增加元素
     */
    void add(int index,E element);
    /**
     * 删:通过索引删除元素,并返回删除的值
     */
    E removeByIndex(int index);
    /**
     * 删:通过值删除元素:删除第一个值为element的元素
     */
    void removeByValue(E element);
    /**
     * 删:删除所有值为element的元素
     */
    void removeAllValue(E element);
    /**
     * 改:在索引为index的位置设置值,并返回该值
     */
    E set(int index,E elemnet);
    /**
     * 查:查询索引为index的值,并返回查询到的值
     */
    E get(int index);
    /**
     * 判断当前线性表中是否包含该元素值
     */
    boolean contains(E element);
    /**
     * 查询值为element的索引并返回该值
     */
    int indexOf(E element);
}

🌟SeqList接口的实现类:MyArray

    三步原则:判断索引合法性——>数据处理:增删改查操作——>判断是否需要扩容

🌈 初始化

        初始化主要包括数组,有效元素的个数size,以及有参和无参构造用来初始化数组长度的。

注意:size不仅表示数组中有效元素的个数,而且表示数组中下一个元素的索引。

    /**
     * 定义一个泛型数组elementData,使用Object类型
     * size不仅表示现有数组实际存储的大小,还表示下一个元素的位置
     */
    private Object[] elementData;//定义一个泛型数组
    private int size;//定义一个默认的数组大小
    /**
     * 有参和无参构造方法
     */
    //无参构造: 默认数组的长度是10
    public MyArray() {
        this(10);
    }
    //有参构造: 传入参数确定动态数组的长度为size
    public MyArray(int size) {
        this.elementData = new Object[size];
    }

🌈 元素尾插

         主要思想:直接给数组添加元素,然后判断是否需要扩容。

    @Override
    public void add(E element) {
        this.elementData[size] = element;
        size++;
        if(size==elementData.length){
            grow();
        }
    }

 🌈 自定义实现grow扩容方法     

       主要思想:获取数组原来的旧长度,定义数组扩容后的新长度,定义新数组,长度为扩容后的长度,并将旧数组的元素值拷贝过来,此时的新数组就是我们的数组。

    private void grow() {
        int oldLength = elementData.length;//获取原数组的长度
        int newLength = oldLength<<1;//获取新数组的长度:默认将容量扩为原先的一倍
        Object[] newArray = Arrays.copyOf(elementData, newLength);//将原数组拷贝到新数组中
        elementData = newArray;//获取扩容后的数组
    }

🌈 在索引位置添加元素(重点)


     主要思想:判断索引的合法性——>判断根据索引插入元素的位置判断是否需要扩容:
     (1)如果index=size,插入位置在size,不需要扩容:直接调用尾插方法
     (2)如果0<index<size插入位置在中间:遍历数组,找到索引位置,将索引后的数组元素每个都向后移动一位,就可以将索引位置空出来。 然后再将元素添加到索引位置。——>添加完成后判断是否需要扩容。
    

    @Override
    public void add(int index, E element) {
        //(1)判断索引的合理性
        if(index<0||index>size){
            throw new IllegalArgumentException("add index illegal");
        }
        //(2)如果插入位置为尾插:则直接调用尾插方法并提前结束该方法
        if(index==size){
            add(element);
            return;
        }
        //(3)如果插入位置在元素中间,则将数据插入并判断数组长度是否需要扩容
        for (int i = size-1; i >=index; i--) {//遍历数组走到Index位置
            elementData[i+1] = elementData[i];//将Index后的元素向后移
        }
        elementData[index]=element;//此时Index空出,插入待添加的元素
        //(4)判断数组是否需要扩容
        size++;
        if(size==elementData.length){
            grow();
        }
    }

🌈 删除索引位置元素

        主要思路:遍历数组走到index位置,获取要删除的元素值进行记录,并将索引后的元素向左移动一位进行元素的覆盖。

 @Override
    public E removeByIndex(int index) {
        //(1)判断索引的合法性
        if(!indexCheck(index)){
           throw  new IllegalArgumentException("remove index illegal");
        }
        //(2)获取要删除索引位置的元素
        E oldVal = (E)elementData[index];
        //(2)遍历数组从Index位置走到size
        for (int i = index; i < size-1; i++) {//注意:这里因为要保证索引不能越界:i+1实际上是取不到size的,也就是i<size-1
            elementData[i] = elementData[i+1];//将索引后的元素向左移动一位进行元素的覆盖
        }
        //(3)size--
        size--;
        return oldVal;
    }
    /**
     * 索引的合法性校正
     */
    private boolean indexCheck(int index){
        if(index<0 || index>=size){
            return false;
        }
        return true;
    }

🌈 删除第一个值为element的元素

        主要思路:遍历数组从0索引开始一直判断元素值是否等于element。如果找到了element,则该元素是我们要删除的元素。获取element位置下的Index,通过调用removeByIndex方法删除。更新元素个数。

 @Override
    public void removeByValue(E element) {
        for (int i = 0; i < size; i++) {
            if(elementData[i].equals(element)){
                removeByIndex(i);
                return;
            }
        }
        throw new IllegalArgumentException("没有找到该元素~");
    }

🌈 删除所有值为element的元素(重点)

        注意: 错误写法:去掉removeByValue方法中的return即可。
        原因:当数组中要删除的元素是连续出现时(比如连续出现了3次222),调用removeByIndex方法删除元素要考虑当前指向的元素是不是要删除的元素:
       如果是要删除的元素,则i不能变化
       如果不是要删除的元素,i才可以++;

    @Override
    public void removeAllValue(E element) {
//         自己一开始写成了这种形式,当出现连续要删除的元素值时会发生漏删的现象
      /*  for (int i = 0; i < size; i++) {
            if(elementData[i].equals(element)){
                removeByIndex(i);
            }
        }*/
        for (int i = 0; i < size; ) {
            if(elementData[i].equals(element)){
                removeByIndex(i);
            }else{
                i++;
            }
        }
    }

  🌈改:修改index位置下的元素,并返回被修改的原值

    @Override
    public E set(int index, E element) {
        if(!indexCheck(index)){
            throw new IllegalArgumentException("set index illegal");
        }
        //找到index下的旧值返回,并将新的数值赋值给数组的index位置
        E oldVal = (E)elementData[index];
        elementData[index] = element;
        return oldVal;

    }

 🌈查:查询索引index位置下的元素

     @Override
    public E get(int index) {
        //(1)判断索引的合法性
        if(!indexCheck(index)){
            throw new IllegalArgumentException("get index illegal");
        }
        //(2)返回数值
        return (E) elementData[index];
    }

🌈 判断数组是否包含element元素值,返回判断结果。

      主要思想:遍历数组0-size,将数组的每一个值与element进行比对。

  @Override
    public boolean contains(E element) {
        for (int i = 0; i < size; i++) {
            if(elementData[i].equals(element)){
                return true;
            }
        }
        return false;
    }

🌈 返回元素element在数组中对应的索引

 @Override
    public int indexOf(E element) {
        for (int i = 0; i < size; i++) {
            if(elementData[i].equals(element)){
                return i;
            }
        }
        throw new IllegalArgumentException("indexOf error"+"Don't has this element");
    }

 🌈 重写ToString方法

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        // 遍历elementData数组
        for (int i = 0; i < size; i++) {
            sb.append(elementData[i]);
            if (i < size - 1) {
                sb.append(", ");
            }
        }
        sb.append("]");
        return sb.toString();
    }

🌟完整代码实现

public class MyArray<E> implements SeqList<E> {
    /**
     * 定义一个泛型数组elementData,使用Object类型
     * size不仅表示现有数组实际存储的大小,还表示下一个元素的位置
     */
    private Object[] elementData;//定义一个泛型数组
    private int size;//定义一个默认的数组大小
    /**
     * 有参和无参构造方法
     */
    //无参构造: 默认数组的长度是10
    public MyArray() {
        this(10);
    }
    //有参构造: 传入参数确定动态数组的长度为size
    public MyArray(int size) {
        this.elementData = new Object[size];
    }
    /**
     * 尾插:直接给数组添加元素
     */
    @Override
    public void add(E element) {
        this.elementData[size] = element;
        size++;
        if(size==elementData.length){
            grow();
        }
    }
    /**
     * 自定义实现grow扩容方法
     * 思想:
*          获取数组原来的旧长度,定义数组扩容后的新长度,定义新数组,长度为扩容后的长度,并将旧数组的
     * 元素值拷贝过来,此时的新数组就是我们的数组。
     */
    private void grow() {
        int oldLength = elementData.length;//获取原数组的长度
        int newLength = oldLength<<1;//获取新数组的长度:默认将容量扩为原先的一倍
        Object[] newArray = Arrays.copyOf(elementData, newLength);//将原数组拷贝到新数组中
        elementData = newArray;//获取扩容后的数组
    }
    /**
     * 三步原则:判断索引合法性——>数据处理:增删改查操作——>判断是否需要扩容
     * 增:根据索引添加元素
     * 思想:判断索引的合法性——>判断根据索引插入元素的位置判断是否需要扩容:
     *     (1)如果index=size,插入位置在size,不需要扩容:直接调用尾插方法
     *     (2)如果0<index<size插入位置在中间:遍历数组,找到索引位置,将索引后的数组元素每个都向后移动一位,就可以将索引位置空出来
     * 然后再将元素添加到索引位置。——>添加完成后判断是否需要扩容
     */
    @Override
    public void add(int index, E element) {
        //(1)判断索引的合理性
        if(index<0||index>size){
            throw new IllegalArgumentException("add index illegal");
        }
        //(2)如果插入位置为尾插:则直接调用尾插方法并提前结束该方法
        if(index==size){
            add(element);
            return;
        }
        //(3)如果插入位置在元素中间,则将数据插入并判断数组长度是否需要扩容
        for (int i = size-1; i >=index; i--) {//遍历数组走到Index位置
            elementData[i+1] = elementData[i];//将Index后的元素向后移
        }
        elementData[index]=element;//此时Index空出,插入待添加的元素
        //(4)判断数组是否需要扩容
        size++;
        if(size==elementData.length){
            grow();
        }
    }

    /**
     * 删:通过索引删除元素
     * 思路:遍历数组走到index位置,获取要删除的元素值进行记录,并将索引后的元素向左移动一位进行元素的覆盖
     */
    @Override
    public E removeByIndex(int index) {
        //(1)判断索引的合法性
        if(!indexCheck(index)){
           throw  new IllegalArgumentException("remove index illegal");
        }
        //(2)获取要删除索引位置的元素
        E oldVal = (E)elementData[index];
        //(2)遍历数组从Index位置走到size
        for (int i = index; i < size-1; i++) {//注意:这里因为要保证索引不能越界:i+1实际上是取不到size的,也就是i<size-1
            elementData[i] = elementData[i+1];//将索引后的元素向左移动一位进行元素的覆盖
        }
        //(3)size--
        size--;
        return oldVal;
    }
    /**
     * 索引的合法性校正
     */
    private boolean indexCheck(int index){
        if(index<0 || index>=size){
            return false;
        }
        return true;
    }
    /**
     * 删:直接删除数组中的出现的第一个element
     * 思路:遍历数组从0索引开始一直判断元素值是否等于element。如果找到了element,则该元素是我们要删除的元素
     * 获取element位置下的Index,通过调用removeByIndex方法删除。size--。
     */
    @Override
    public void removeByValue(E element) {
        for (int i = 0; i < size; i++) {
            if(elementData[i].equals(element)){
                removeByIndex(i);
                return;
            }
        }
        throw new IllegalArgumentException("没有找到该元素~");
    }
    /**
     * 删:删除数组中所有值为element的元素:
     * 错误写法:去掉removeByValue方法中的return即可
     * 原因:当数组中要删除的元素是连续出现时(比如连续出现了3次222),调用removeByIndex方法删除元素要考虑当前指向的元素是不是要删除的元素:
     * 如果是要删除的元素,则i不能变化
     * 如果不是要删除的元素,i才可以++;
     */
    @Override
    public void removeAllValue(E element) {
//         自己一开始写成了这种形式,当出现连续要删除的元素值时会发生漏删的现象
      /*  for (int i = 0; i < size; i++) {
            if(elementData[i].equals(element)){
                removeByIndex(i);
            }
        }*/
        for (int i = 0; i < size; ) {
            if(elementData[i].equals(element)){
                removeByIndex(i);
            }else{
                i++;
            }
        }
    }
    /**
     * 改:修改index位置下的元素为element,并返回被修改的原值
     */
    @Override
    public E set(int index, E element) {
        if(!indexCheck(index)){
            throw new IllegalArgumentException("set index illegal");
        }
        //找到index下的旧值返回,并将新的数值赋值给数组的index位置
        E oldVal = (E)elementData[index];
        elementData[index] = element;
        return oldVal;

    }
    /**
     * 查:查询索引Index位置下的元素
     */
    @Override
    public E get(int index) {
        //(1)判断索引的合法性
        if(!indexCheck(index)){
            throw new IllegalArgumentException("get index illegal");
        }
        //(2)返回数值
        return (E) elementData[index];
    }

    /**
     * 判断数组是否包含element元素值,返回判断结果
     * 思想:遍历数组0-size,将数组的每一个值与element进行比对
     */
    @Override
    public boolean contains(E element) {
        for (int i = 0; i < size; i++) {
            if(elementData[i].equals(element)){
                return true;
            }
        }
        return false;
    }
    /**
     * 返回元素element在数组中对应的索引
     */
    @Override
    public int indexOf(E element) {
        for (int i = 0; i < size; i++) {
            if(elementData[i].equals(element)){
                return i;
            }
        }
        throw new IllegalArgumentException("indexOf error"+"Don't has this element");
    }
    /**
     * 重写ToString方法
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        // 遍历elementData数组
        for (int i = 0; i < size; i++) {
            sb.append(elementData[i]);
            if (i < size - 1) {
                sb.append(", ");
            }
        }
        sb.append("]");
        return sb.toString();
    }
}

🌟测试类

public class MyArrayTest {
    public static void main(String[] args) {
        //注意这里的写法:SeqList是一个接口,我们不能直接创建它的对象,必须通过它的实现类来创建对象
        SeqList<Integer> list = new MyArray<>() ;
        //往集合中添加元素
        list.add(0);
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(3);
        list.add(3);
        System.out.println(list);
        //在索引位置添加元素
        list.add(3,9);
        System.out.println(list);
        //通过索引删除数据
        list.removeByIndex(3);
        System.out.println(list);
        //删除出现的第一个element值
        list.removeByValue(3);
        System.out.println(list);
        //删除所有值为element的元素
        list.removeAllValue(3);
        System.out.println(list);
        //将数组index 为0的位置改为9
        System.out.println(list.set(0,9));
        System.out.println(list);
        //查询Index下的元素
        System.out.println(list.get(3));
        //判断元素值7在数组中是否包含
        System.out.println(list.contains(7));
        //返回元素在数组中对应的索引值
        System.out.println(list.indexOf(9));
    }
}

代码运行结果:


🌟总结 

  • 动态数组在中间位置进行插入和删除,操作比较耗时,因为此时需要来回搬移元素;但是不能片面的认为数组的插入就一定慢:在进行尾插时,速度也是很快的,因为不需要元素的搬移。
  • 动态数组的底层依然使用的是数组,因此按索引查询是比较快的。
  • 动态数组一般需要连续的空间,因此当数据量较大时,会存在很大一部分空间的浪费。比如此时数组的长度为100,数组存满了,要再保存一个元素,就要进行扩容处理。扩容之后的数组长度变为了200.但是此时为了存储一个元素,浪费了99个空间。

        玩了两天,今天终于将动态数组的增删改查和扩容机制写了一遍,这两天就是说疯狂的补课补课补作业中~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值