【数据结构】用Java实现动态数组

1.线性表

线性表( linear list是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

 

2.顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

2.1 接口的实现

使用泛型更好的接收不同数据类型(一个顺序表只能接受一种类型!)
public interface SeqList<E> {
    //    尾插
    void add(E element);
    //    将 element 插入到 index 位置
    void add(int index,E element);

    //    删除 index 位置元素<返回删除的值
    E removeByIndex(int index);
    //    删除第一个值element的元素
    void removeByValue(E element);
    //    删除所有值element的元素
    void removeAllValue(E element);

    //    将下标 index 位置元素设置为 element,返回替换前的值
    E set(int index,E element);
    E get(int index);
    //    判断 o 是否在其中
    boolean contains(E element);
    int indexOf(E element);
    int size();
    void clear();

}

3.自己动手实现ArrayList

3.1 重写SeqList接口方法

public class MyArray<E> implements SeqList<E> {
    private Object[] elementData;
    //    保存目前线性表中真正保存的有效元素个数
    private int size;
    //    默认开辟数组长度10
    public MyArray(){
        this(10);
    }
    //    从外部传入一个数组长度进行初始化
    public MyArray(int size){
        this.elementData = new Object[size];
    }
    @Override
    public void add(E element) {}   
    @Override
    public void add(int index, E element) {}
    @Override
    public E removeByIndex(int index) {}
    @Override
    public void removeByValue(E element) {}
    @Override
    public void removeAllValue(E element) {}
    @Override
    public E set(int index, E element) {}
    @Override
    public boolean contains(E element) {}
    @Override
    public int indexOf(E element) {}
    @Override
    public String toString() {}
}

3.2 在数组最后新增元素

(1)将下标为size的elementData数组进行赋值element

(2)size长度加1

(3)判断数组是否满了,实际长度是否等于数组长度,如果是,进行扩容

为什么不先判断数组是否满了再进行添加?

我们使用的是add()方法对数组进行添加操作的,每添加一次都会判断数组是否满了,为下一个元素添加做准备,因此每次添加时,数组一定是没满的,size下标是可以存放元素的

public void add(E element) {
//        size恰好是下一个保存属性的下标
        this.elementData[size] = element;
        size ++;
//        实际长度 == 数组长度,对数组进行扩容
        if(size == elementData.length){
            grow();
        }
    }

3.3 对内部数组进行扩容

判断数组满了的时候,对数组扩容

对原来长度左移一位,新长度为原来的两倍

再使用Arrays.copyOf对原数组进行拷贝

private void grow(){
        int oldLength = elementData.length;
        int newLength = oldLength << 1;
        Object[] newArray = Arrays.copyOf(elementData,newLength);
        elementData = newArray;
    }

3.4 在 index位置新增元素

(1)判断index是否合法,不合法抛出IllegalArgumentException异常
       (index < 0 || index > size)
(2)如果index刚好是size,那就是再数组的最后一位添加,直接调用add()方法就好了
(3)将下标为index以及之后的元素往后移一位,再将index下标的元素改为element
        比如:原数组为 0 1 2 3 4 5,在index为2的下标插入元素6,首先将下标为index以及之后的元素往后移一位:0 1 2 2 3 4 5,再将index下标的元素改为element:0 1 6 2 3 4 5
(4)判断数组是否满了,实际长度是否等于数组长度,如果是,进行扩容
public void add(int index, E element) {
//        判断index是否合法??
        if(!rangeCheck(index)){
            throw new IllegalArgumentException("add index Illegal");
        }
//        尾插
        if(index == size){
            add(element);
            return;
        }
//        0 <= index <size
        for (int i = size -1; i >= index ; i--) {
            elementData[i +1] = elementData[i];
        }
//        装入插入的元素
        elementData[index] = element;
        size ++;
        if(size == elementData.length){
            grow();
        }
    }

3.5 对下标的合法性校验

private boolean rangeCheck(int index){
        if (index < 0 || index >= size) {
            return false;
        }
        return true;
    }

3.6 删除第一次出现的下标为index的元素

(1)判断index是否合法,不合法抛出IllegalArgumentException异常

(2)从待删除元素开始,将当前位置的值覆盖为下一个元素的值

比如原数组为 0 1 2 3 4 5,待删除元素下标为2,对元素进行覆盖:0,1,3,4,5,5

(3)size减1,有效数组元素为0,1,3,4,5,返回修改前的值

(4)如果是引用类型,需要将最后一个位置的元素置为空,否则会出现内存泄漏!!!

public E removeByIndex(int index) {
        // 1.index合法性校验
        if(!rangeCheck(index)) {
            throw new IllegalArgumentException("remove index illegal!");
        }
        E oldVal = (E) elementData[index];
        // 从待删除元素开始,将当前位置的值覆盖为下一个元素的值
        // 由于删除场景下索引不能取到size
        // 因此要保证代码中所有位置均不越界,i + 1 < size
        for (int i = index; i < size - 1; i++) {
            elementData[i] = elementData[i + 1];
        }
        // 维护一下size属性,最后多的那个元素不影响我们的使用~下次添加元素时就会自动覆盖掉~
        size --;
        return oldVal;
    }

3.7 删除第一个值element的元素

从前往后找到这个元素的下标,使用removeByIndex(index)方法进行删除,删除后退出方法

public void removeByValue(E element) {
        // 从前向后遍历,碰到第一个值为待删除的元素时,删除该元素并返回 ~
        for (int i = 0; i < size; i++) {
            if (elementData[i].equals(element)) {
                // 碰到第一个待删除的元素
                removeByIndex(i);
                return;
            }
        }
        System.out.println("没有待删除的元素!");
    }

3.8 删除所有值element的元素

从前往后找到这个元素的下标,使用removeByIndex(index)方法进行删除

为什么不能删除一次就i++?

举个例子说明:原数组为 0 1 2 2 3 4 5,待删除的元素为2,当我遍历到第一个待删除的元素2时,i为2,此时调用 removeByIndex(i)进行删除,删除后数组元素为 0 1 2 3 4 5 ,如果进行i++,那么此时i为3,就会少删除现在数组中的2(下标为2)。

public void removeAllValue(E element) {
        // 从前向后遍历,碰到索引对应的元素为待删除的元素,删除即可
        for (int i = 0; i < size;) {
            if (elementData[i].equals(element)){
                // 当i指向的元素是待删除的元素,不能移动i
                removeByIndex(i);
            }else {
                // 此时i指向的不是待删除元素,此时移动i的指向,继续判断下一个元素是否是待删除的元素
                i ++;
            }
        }
    }

3.9 修改下标为index的元素为element

public E set(int index, E element) {
        if(!rangeCheck(index)) {
            throw new IllegalArgumentException("set index illegal!");
        }
        E oldVal = (E) elementData[index];
        elementData[index] = element;
        return oldVal;
    }

3.10 获取下标为index的元素

public E get(int index) {
        if (!rangeCheck(index)) {
            throw new IllegalArgumentException("get index illegal!");
        }
        return (E) elementData[index];
    }

3.11 获取元素值为element的下标

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

3.12 判断数组中是否存在element

 public boolean contains(E element) {
        // foreach会遍历所有的数组元素,包括无效元素!
        // 无效元素此时为null,出现空指针异常
//        for (Object e : elementData) {
//            if (e.equals(element)) {
//                return true;
//            }
//        }
//        return false;

        for (int i = 0; i < size; i++) {
            if (elementData[i].equals(element)) {
                return true;
            }
        }
        return false;
    }

3.13 打印顺序表

public String toString() {
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < size; i++) {
            sb.append(elementData[i]);
            if(i != size - 1){
                sb.append(",");
            }
        }

        sb.append("]");
        return sb.toString();
    }

3.14 获取顺序表长度以及清空顺序表

    // 获取顺序表长度
    public int size() {
        return this.usedSize;
    }

    // 清空顺序表
    public void clear() {
        /*
        如果是引用数据类型 得一个一个置为空 这样做才是最合适的
        for (int i = 0; i < this.usedSize; i++) {
            this.elem[i] = null;
        }
        this.usedSize = 0;
        */
        for (int i = 0; i < usedSize; i++) {
            elem[i] = 0;
        }
        this.usedSize = 0;
    }

4.MyArray整体实现

4.1 MyArray类

import java.util.Arrays;
import java.util.NoSuchElementException;

public class MyArray<E> implements SeqList<E> {

    private Object[] elementData;
    //    保存目前线性表中真正保存的有效元素个数
    private int size;

    //    默认开辟数组长度10
    public MyArray(){
        this(10);
    }
    //    从外部传入一个数组长度进行初始化
    public MyArray(int size){
        this.elementData = new Object[size];
    }

    @Override
    public void add(E element) {
//        size恰好是下一个保存属性的下标
        this.elementData[size] = element;
        size ++;
//        实际长度 = 数组长度
        if(size == elementData.length){
            grow();
        }
    }
    //    对内部数组进行扩容
    private void grow(){
        int oldLength = elementData.length;
        int newLength = oldLength << 1;
        Object[] newArray = Arrays.copyOf(elementData,newLength);
        elementData = newArray;

    }

    @Override
    public void add(int index, E element) {
//        判断index是否合法??
        if(index < 0 || index > size){
            throw new IllegalArgumentException("add index Illegal");
        }
//        尾插
        if(index == size){
            add(element);
            return;
        }
//        0 <= index <size
        for (int i = size -1; i >= index ; i--) {
            elementData[i +1] = elementData[i];
        }
//        装入插入的元素
        elementData[index] = element;
        size ++;
        if(size == elementData.length){
            grow();
        }

    }

    @Override
    public E removeByIndex(int index) {
        // 1.index合法性校验
        if(!rangeCheck(index)) {
            throw new IllegalArgumentException("remove index illegal!");
        }
        E oldVal = (E) elementData[index];
        // 从待删除元素开始,将当前位置的值覆盖为下一个元素的值
        // 由于删除场景下索引不能取到size
        // 因此要保证代码中所有位置均不越界,i + 1 < size
        for (int i = index; i < size - 1; i++) {
            elementData[i] = elementData[i + 1];
        }
        // 维护一下size属性,最后多的那个元素不影响我们的使用~下次添加元素时就会自动覆盖掉~
        size --;
        return oldVal;
    }

    @Override
    public void removeByValue(E element) {
        // 从前向后遍历,碰到第一个值为待删除的元素时,删除该元素并返回 ~
        for (int i = 0; i < size; i++) {
            if (elementData[i].equals(element)) {
                // 碰到第一个待删除的元素
                removeByIndex(i);
                return;
            }
        }
        System.out.println("没有待删除的元素!");
    }

    @Override
    public void removeAllValue(E element) {
        // 从前向后遍历,碰到索引对应的元素为待删除的元素,删除即可
        for (int i = 0; i < size;) {
            if (elementData[i].equals(element)){
                // 当i指向的元素是待删除的元素,不能移动i
                removeByIndex(i);
            }else {
                // 此时i指向的不是待删除元素,此时移动i的指向,继续判断下一个元素是否是待删除的元素
                i ++;
            }
        }

    }

    @Override
    public E set(int index, E element) {
        if(!rangeCheck(index)) {
            throw new IllegalArgumentException("set index illegal!");
        }
        E oldVal = (E) elementData[index];
        elementData[index] = element;
        return oldVal;
    }
//    private boolean ran

    @Override
    public E get(int index) {
        if (!rangeCheck(index)) {
            throw new IllegalArgumentException("get index illegal!");
        }
        return (E) elementData[index];
    }

    private boolean rangeCheck(int index){
        if (index < 0 || index >= size) {
            return false;
        }
        return true;
    }

    @Override
    public boolean contains(E element) {
        // foreach会遍历所有的数组元素,包括无效元素!
        // 无效元素此时为null,出现空指针异常
//        for (Object e : elementData) {
//            if (e.equals(element)) {
//                return true;
//            }
//        }
//        return false;

        for (int i = 0; i < size; i++) {
            if (elementData[i].equals(element)) {
                return true;
            }
        }
        return false;
    }

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

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

        }

        sb.append("]");
        return sb.toString();

    }

    // 获取顺序表长度
    public int size() {
        return this.size;
    }

    // 清空顺序表
    public void clear() {
        /*
        如果是引用数据类型 得一个一个置为空 这样做才是最合适的
        for (int i = 0; i < this.usedSize; i++) {
            this.elem[i] = null;
        }
        this.usedSize = 0;
        */
        for (int i = 0; i < size; i++) {
            elementData[i] = 0;
        }
        this.size = 0;
    }
}

4.2 Test类

package seqlist.array;

import seqlist.SeqList;

public class MyArrayTest {
    public static void main(String[] args) {
        SeqList<Integer> list = new MyArray<Integer>(4);
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(9);
        list.add(10);
        list.add(10);
        list.add(6);
        list.add(10);
        list.add(5);
        list.add(7);
        list.add(10);
        list.add(10);

        System.out.println(list);
        System.out.println("------------添加测试-----------");
        System.out.println("从数组尾部添加99");
        list.add(99);
        System.out.println("添加index为2,element为88");
        list.add(2,88);
        System.out.println(list);
        System.out.println("-----------删除测试------------");
        System.out.println("删除index为0");
        list.removeByIndex(0);
        System.out.println("删除元素为6");
        list.removeByValue(6);
        System.out.println("删除所有10");
        list.removeAllValue(10);
        System.out.println(list);
        System.out.println("-----------其他------------");
        System.out.println("查看是否包含10这个元素");
        System.out.println(list.contains(10));
        System.out.println("修改index为3,element为19");
        list.set(3,19);
        System.out.println("获取index为3的元素");
        System.out.println(list.get(3));
        System.out.println(list);
        System.out.println("获取element为88的index");
        System.out.println(list.indexOf(88));
        System.out.println("获取顺序表长度");
        System.out.println(list.size());
        System.out.println("清空顺序表");
        list.clear();
        System.out.println(list);

    }
}

4.3 测试结果

趁热打铁:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值