百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

5张图搞懂:动态数组的实现(Java版本)

ccwgpt 2024-11-02 10:57 20 浏览 0 评论

静态数组

Java中最基本的数组大家肯定不会陌生:

int[] array = new int[6];
for (int i = 0; i < array.length; i++){
    array[i] = 2 * i + 1;
}

通过循环把元素放入指定的位置中,类似于这样(图用 WPS 顺手拖的,先凑合着看下,意思应该到位了 ):


这是一个静态数组,因为我们在第一步初始化的时候就已经固定了它的长度,后面再也无法改变。所以,由于有这个限制,静态数组不适用于那些不确定储存多少数据的场景。
但是如果数组满了,能否再新建一个更长一些的数组,把原数组这些元素再转移到新数组中呢?这样一来,数组就可以继续使用了。按照这个思路,我们就可以创建基于静态数组的动态数组。

动态数组的实现原理

“动态”主要体现在以下几方面:

1.添加元素

不局限于只在数组末尾添加,而是能够随意选择索引位置(只要不超过数组长度)。例如在索引为1处添加元素4:


从图中可以看出,需要将index处及右侧的元素依次向右移动一个单位(从末位元素开始),最后用新增元素覆盖index处元素。

2.删除元素

同添加元素,也可根据索引进行选择。例如删除索引为0处的元素3:


删除元素移动元素的方向与添加元素正好相反,从index处开始,直接使用后一位元素覆盖前一位元素,最后将末位元素置为null。

3.数组扩容

数组一旦装满元素,可触发数组扩容,即新建一个更长的数组,将原数组元素转移到新数组中,并将引用指向新数组,完成数组的变更;

4.数组缩减

如果数组元素相对总容量来说过少(例如数组元素个数小于数组容量的1/4),便可触发数组缩减,即新建一个更短的数组,并转移元素至新数组。

代码实现

以下通过新建一个 Array 类,依次实现这几个重要功能:

public class Array<E> {
    private E[] data;       // 使用静态数组存放数组元素
    private int size;       // 记录数组元素数量

    public Array(int capacity) {
        this.data = (E[]) new Object[capacity];
        this.size = 0;
    }

    public Array() {
        this(10);   // 默认capacity为10
    }

    // 数组扩容/缩减
    public void resize(int newCapacity) {
        // 新数组长度必须大于0
        if (newCapacity < 0) throw new IllegalArgumentException("capacity must > 0!");
        // 创建新数组
        E[] newData = (E[]) new Object[newCapacity];
        // 将原数组元素放入新数组中
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        // 将引用指向新数组
        data = newData;
    }

    /**
     * 在指定位置添加元素
     * 指定位置处的元素需要向右侧移动一个单位
     * @param index   索引
     * @param element 要添加的元素
     */
    public void add(int index, E element) {
        if (index < 0 || index > size) throw new IllegalArgumentException("Illegal index, index must > 0 and <= size!");
        // 数组满员触发扩容
        if (size == data.length) {
            resize(2 * data.length);  // 扩容为原数组的2倍
        }
        // 从尾部开始,向右移动元素,直到index
        for (int i = size - 1; i >= index; i--) {
            data[i + 1] = data[i];
        }
        // 添加元素
        data[index] = element;
        size++;
    }

    // 数组头部添加元素
    public void addFirst(E element) {
        add(0, element);
    }

    // 数组尾部添加元素
    public void addLast(E element) {
        add(size, element);
    }

    /**
     * 删除指定位置元素
     * 通过向左移动一位,覆盖指定位置处的元素,实现删除元素(data[size - 1] = null)
     * @param index 索引
     */
    public E remove(int index) {
        if (index < 0 || index > size) throw new IllegalArgumentException("Illegal index, index must > 0 and < size!");
        // 数组长度为0时抛出异常
        if (size == 0) throw new IllegalArgumentException("Empty array!");
        E removedElement = data[index];
        // 向左移动元素
        for (int i = index; i < size - 1; i++) {
            data[i] = data[i + 1];
        }
        // 将尾部空闲出的位置置为空,释放资源
        data[size - 1] = null;
        size--;
        // size过小触发数组缩减
        if (size == data.length / 4 && data.length / 2 != 0) resize(data.length / 2);
        return removedElement;
    }

    // 删除头部元素
    public E removeFirst() {
        return remove(0);
    }

    // 删除尾部元素
    public E removeLast() {
        return remove(size - 1);
    }

    // 重写Override方法,自定义数组显示格式
    @Override
    public String toString() {
        StringBuilder str = new StringBuilder();
        // 显示数组的整体情况(长度、总容量)
        str.append(String.format("Array: size = %d, capacity = %d\n[", size, data.length));
        // 循环添加数组元素至str
        for (int i = 0; i < size; i++) {
            str.append(data[i]);
            if (i < size - 1) str.append(", ");
        }
        str.append("]");
        return str.toString();
    }
}

接下来我们测试一下这个数组的使用情况:

    public static void main(String[] args) {
        // 添加10个元素
        Array<Integer> arr = new Array<>();
        for (int i = 0; i < 10; i++)
            arr.add(i, i);
        // 查看数组当前状态
        System.out.println(arr);
        // 继续添加元素,观察是否扩容
        arr.add(arr.size, 7);
        System.out.println(arr);

        // 再删除6个元素,观察是否缩减
        for (int i = 0; i < 6; i++) {
            System.out.println("元素" + arr.removeFirst() + "已被删除!");
        }
        System.out.println(arr);
    }

/*
输出结果:
Array: size = 10, capacity = 10
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Array: size = 11, capacity = 20
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 7]
元素0已被删除!
元素1已被删除!
元素2已被删除!
元素3已被删除!
元素4已被删除!
元素5已被删除!
Array: size = 5, capacity = 10
[6, 7, 8, 9, 7]
*/

可以看到,当数组满员后,继续添加元素可以成功触发数组扩容;而当数组元素过少时,也会触发缩减。
再实现几个常用方法来完善我们的动态数组类:

    // 获取数组长度
    public int getSize() {
        return size;
    }

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

    // 判断数组是否为空
    public boolean isEmpty() {
        return getSize() == 0;
    }

    // 查找指定元素在数组中的位置
    public int search(E element) {
        for (int i = 0; i < getSize(); i++) {
            if (data[i].equals(element)) {
                return i;
            }
        }
        // -1表示未找到
        return -1;
    }

    // 判断指定元素是否在数组中
    public boolean contains(E element) {
        return search(element) != -1;
    }

    // 按照索引查找元素值
    public E get(int index) {
        if (index < 0 || index > size) throw new IllegalArgumentException("Illegal index, index must > 0 and < size!");
        return data[index];
    }

    // 查找头部元素
    public E getFirst() {
        return get(0);
    }

    // 查找尾部元素
    public E getLast() {
        return get(getSize() - 1);
    }

    // 设置指定位置的元素值
    public void set(int index, E element) {
        if (index < 0 || index > size) throw new IllegalArgumentException("Illegal index, index must > 0 and < size!");
        data[index] = element;
    }

    /**
     * 按照元素值删除
     * 只删除数组中第一个元素值与指定值相等的元素
     * @param element 指定元素值
     */
    public boolean removeElement(E element) {
        int index = search(element);
        if (index != -1) {
            remove(index);
            return true;
        }
        return false;
    }

    /**
     * 按照元素值删除
     * 删除数组中所有值与指定值相等的元素
     *
     * @param element 指定元素值
     */
    public boolean removeElementAll(E element) {
        boolean isRemoved = false;
        int i = getSize() - 1;
        while (i >= 0) {
            if (data[i].equals(element)) {
                remove(i);
                isRemoved = true;
            }
            i--;
        }
        return isRemoved;
    }

从外部调用者的角度,无法觉察到其中的数组变更操作,感觉就是一个动态数组,但是由于扩容和缩减操作均需要新建数组,并且遍历原数组,会导致过多的开销,所以从性能上来说,并不是好的解决方案。后面我们将学习更加高效的数据结构。

相关推荐

团队管理“布阵术”:3招让你的团队战斗力爆表!

为何古代军队能够以一当十?为何现代企业有的团队高效似“特种部队”,有的却松散若“游击队”?**答案正隐匿于“布阵术”之中!**今时今日,让我们从古代兵法里萃取3个核心要义,助您塑造一支战斗力爆棚的...

知情人士回应字节大模型团队架构调整

【知情人士回应字节大模型团队架构调整】财联社2月21日电,针对原谷歌DeepMind副总裁吴永辉加入字节跳动后引发的团队调整问题,知情人士回应称:吴永辉博士主要负责AI基础研究探索工作,偏基础研究;A...

豆包大模型团队开源RLHF框架,训练吞吐量最高提升20倍

强化学习(RL)对大模型复杂推理能力提升有关键作用,但其复杂的计算流程对训练和部署也带来了巨大挑战。近日,字节跳动豆包大模型团队与香港大学联合提出HybridFlow。这是一个灵活高效的RL/RL...

创业团队如何设计股权架构及分配(创业团队如何设计股权架构及分配方案)

创业团队的股权架构设计,决定了公司在随后发展中呈现出的股权布局。如果最初的股权架构就存在先天不足,公司就很难顺利、稳定地成长起来。因此,创业之初,对股权设计应慎之又慎,避免留下巨大隐患和风险。两个人如...

消息称吴永辉入职后引发字节大模型团队架构大调整

2月21日,有消息称前谷歌大佬吴永辉加入字节跳动,并担任大模型团队Seed基础研究负责人后,引发了字节跳动大模型团队架构大调整。多名原本向朱文佳汇报的算法和技术负责人开始转向吴永辉汇报。简单来说,就是...

31页组织效能提升模型,经营管理团队搭建框架与权责定位

分享职场干货,提升能力!为职场精英打造个人知识体系,升职加薪!31页组织效能提升模型如何拿到分享的源文件:请您关注本头条号,然后私信本头条号“文米”2个字,按照操作流程,专人负责发送源文件给您。...

异形柱结构(异形柱结构技术规程)

下列关于混凝土异形柱结构设计的说法,其中何项正确?(A)混凝土异形柱框架结构可用于所有非抗震和抗震设防地区的一般居住建筑。(B)抗震设防烈度为6度时,对标准设防类(丙类)采用异形柱结构的建筑可不进行地...

职场干货:金字塔原理(金字塔原理实战篇)

金字塔原理的适用范围:金字塔原理适用于所有需要构建清晰逻辑框架的文章。第一篇:表达的逻辑。如何利用金字塔原理构建基本的金字塔结构受众(包括读者、听众、观众或学员)最容易理解的顺序:先了解主要的、抽象的...

底部剪力法(底部剪力法的基本原理)

某四层钢筋混凝土框架结构,计算简图如图1所示。抗震设防类别为丙类,抗震设防烈度为8度(0.2g),Ⅱ类场地,设计地震分组为第一组,第一自振周期T1=0.55s。一至四层的楼层侧向刚度依次为:K1=1...

结构等效重力荷载代表值(等效重力荷载系数)

某五层钢筋混凝土框架结构办公楼,房屋高度25.45m。抗震设防烈度8度,设防类别丙类,设计基本地震加速度0.2g,设计地震分组第二组,场地类别为Ⅱ类,混凝土强度等级C30。该结构平面和竖向均规则。假定...

体系结构已成昭告后世善莫大焉(体系构架是什么意思)

实践先行也理论已初步完成框架结构留余后人后世子孙俗话说前人栽树后人乘凉在夏商周大明大清民国共和前人栽树下吾之辈已完成结构体系又俗话说青出于蓝而胜于蓝各个时期任务不同吾辈探索框架结构体系经历有限肯定发展...

框架柱抗震构造要求(框架柱抗震设计)

某现浇钢筋混凝土框架-剪力墙结构高层办公楼,抗震设防烈度为8度(0.2g),场地类别为Ⅱ类,抗震等级:框架二级,剪力墙一级,混凝土强度等级:框架柱及剪力墙C50,框架梁及楼板C35,纵向钢筋及箍筋均采...

梁的刚度、挠度控制(钢梁挠度过大会引起什么原因)

某办公楼为现浇钢筋混凝土框架结构,r0=1.0,混凝土强度等级C35,纵向钢筋采用HRB400,箍筋采用HPB300。其二层(中间楼层)的局部平面图和次梁L-1的计算简图如图1~3(Z)所示,其中,K...

死要面子!有钱做大玻璃窗,却没有钱做“柱和梁”,不怕房塌吗?

活久见,有钱做2层落地大玻璃窗,却没有钱做“柱子和圈梁”,这样的农村自建房,安全吗?最近刷到个魔幻施工现场,如下图,这栋5开间的农村自建房,居然做了2个全景落地窗仔细观察,这2个落地窗还是飘窗,为了追...

不是承重墙,物业也不让拆?话说装修就一定要拆墙才行么

最近发现好多朋友装修时总想拆墙“爆改”空间,别以为只要避开承重墙就能随便砸!我家楼上邻居去年装修,拆了阳台矮墙想扩客厅,结果物业直接上门叫停。后来才知道,这种配重墙拆了会让阳台承重失衡,整栋楼都可能变...

取消回复欢迎 发表评论: