数据结构与算法(一): 动态数组

举报
JavaEdge 发表于 2021/06/04 00:28:31 2021/06/04
【摘要】 小码哥数据结构与算法(一): 动态数组 本篇是恋上数据结构与算法(第一季)的学习笔记, 使用JAVA语言 一、数组(Array) 数组是一种顺序存储的线性表,所有元素的内存地址都是连续的 int[] array = new int[]{11, 22, 33}复制代码       在很多编程语言中, 数组有个致命的...

小码哥数据结构与算法(一): 动态数组

本篇是恋上数据结构与算法(第一季)的学习笔记, 使用JAVA语言

一、数组(Array)

  • 数组是一种顺序存储的线性表,所有元素的内存地址都是连续的

  
  1. int[] array = new int[]{11, 22, 33}
  2. 复制代码

 

 

 

在很多编程语言中, 数组有个致命的缺点, 无法动态修改容量
实际开发中我们希望数组的容量是动态变化的

二、动态数组

  • 可以通过数组实现一个动态数组, 动态数组的容量是动态变化的
  • 可以对动态数组进行增删改查操作

  
  1. // 元素的数量
  2. int size();
  3. // 是否为空
  4. boolean isEmpty();
  5. // 是否包含某个元素
  6. boolean contains(E element);
  7. // 添加元素到最后面
  8. void add(E element);
  9. // 返回index位置对应的元素
  10. E get(int index);
  11. // 设置index位置的元素
  12. E set(int index, E element);
  13. // 往index位置添加元素
  14. void add(int index, E element);
  15. // 删除index位置对应的元素
  16. E remove(int index);
  17. // 查看元素的位置
  18. int indexOf(E element);
  19. // 清除所有元素
  20. void clear();
  21. 复制代码

三、动态数组的设计

  • 创建类ArrayList 如下图, 创建size属性来管理数组中元素的个数, 创建elements属性来管理存取的数据

 

 

 


  
  1. public class ArrayList<E> {
  2. private int size;
  3. private E[] elements;
  4. }
  5. 复制代码
  • 添加初始化方法, 创建elements数组, 并指定elements默认的容量

  
  1. public class ArrayList<E> {
  2. private int size;
  3. private E[] elements;
  4. // 设置elements数组默认的初始化空间
  5. private static final int CAPACITY_DEFAULT = 10;
  6. public ArrayList(int capacity) {
  7. capacity = capacity < CAPACITY_DEFAULT ? CAPACITY_DEFAULT : capacity;
  8. elements = (E[]) new Object[capacity];
  9. }
  10. // 默认情况
  11. public ArrayList() {
  12. this(CAPACITY_DEFAULT);
  13. }
  14. }
  15. 复制代码

四、动态数组的实现

1、添加元素

  • 我们知道, 每当数组添加新元素时, 都会在数组最后一个元素的后面添加新元素
  • 这个新元素需要添加到的索引等于当前数组元素的个数, 在ArrayListsize属性就是当前数组元素的个数, 所以就是将新元素添加到数组的size位置上, 然后size1

 

 

 


  
  1. public void add(E element) {
  2. elements[size] = element;
  3. size++;
  4. }
  5. 复制代码

2、数组扩容

  • 由于数组elements最大的容量只有10, 所以当数组存满元素时, 就需要对数组进行扩容
  • 因为数组是无法动态扩容的, 所以需要创建一个新的数组,这个数组的容量要比之前数组的容量大
  • 然后在将原数组中的元素存放到新数组中, 这样就实现了数组的扩容

 

 

 


  
  1. private void ensureCapacity() {
  2. // 获取数组当前容量
  3. int oldCapacity = elements.length;
  4. // 如果 当前存储的元素个数 < 当前数组容量, 直接返回
  5. if (size < oldCapacity) return;
  6. // 新数组的容量为原数组容量的1.5倍
  7. int newCapacity = oldCapacity + (oldCapacity >> 1);
  8. // 创建新数组
  9. E[] newElements = (E[]) new Object[newCapacity];
  10. // 原数组中的元素存储到新数组中
  11. for (int i = 0; i < size; i++) {
  12. newElements[i] = elements[i];
  13. }
  14. // 引用新数组
  15. elements = newElements;
  16. }
  17. 复制代码
  • 此时, add方法的实现如下, 在添加新元素之前, 先判断是否需要扩容

  
  1. public void add(E element) {
  2. // 添加新元素之前, 先判断是否需要扩容
  3. ensureCapacity();
  4. elements[size] = element;
  5. size++;
  6. }
  7. 复制代码

3、删除元素

  • 删除元素, 实际上就是去掉指定位置的元素, 并将后面的元素向前移动
  • 如下图, 当删除索引为3的元素时, 只需要将后面的元素向前移动, 然后在去掉最后一个元素, size1即可

 

 

 


  
  1. public E remove(int index) {
  2. // 取出需要删除的元素
  3. E element = elements[index];
  4. // 通过循环, 将index后面的所有元素向前移动一位
  5. for (int i = index; i < size - 1; i++) {
  6. elements[i] = elements[i + 1];
  7. }
  8. // 删除最后一个元素
  9. elements[--size] = null;
  10. // 将删除的元素返回
  11. return element;
  12. }
  13. 复制代码
  • 注意: 删除元素时传入的索引不能越界, 即不能小于0, 也不能大于等于size
  • 所以我们在删除元素之前需要先进行索引检查

  
  1. private void rangeCheck(int index) {
  2. if (index < 0 || index >= size) {
  3. throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
  4. }
  5. }
  6. 复制代码
  • 此时, remove方法实现如下

  
  1. public E remove(int index) {
  2. // 判断索引是否越界
  3. rangeCheck(index);
  4. // 取出需要删除的元素
  5. E element = elements[index];
  6. // 通过循环, 将index后面的所有元素向前移动一位
  7. for (int i = index; i < size - 1; i++) {
  8. elements[i] = elements[i + 1];
  9. }
  10. // 删除最后一个元素
  11. elements[--size] = null;
  12. // 将删除的元素返回
  13. return element;
  14. }
  15. 复制代码

4、数组缩容

  • 当数组中的元素删除后, 数组剩余的空间可能会很大, 这样就会造成内存的浪费
  • 所以当数组中元素删除后, 我们需要对数组进行缩容
  • 实现方法类似于扩容, 当数组中容量小于某个值时, 创建新的数组, 然后将原有数组中的元素存入新数组即可

  
  1. public void trim() {
  2. // 获取当前数组的容量
  3. int capacity = elements.length;
  4. // 当size大于等于容量的一半, 或则容量已经小于默认容量(10)时, 直接返回
  5. if (size >= capacity >> 1 || capacity < CAPACITY_DEFAULT) return;
  6. // 计算新的容量 = 原有容量的一半
  7. int newCapacity = capacity >> 1;
  8. // 创建新数组
  9. E[] newElements = (E[]) new Object[newCapacity];
  10. // 将原数组元素存入新数组
  11. for (int i = 0; i < size; i++) {
  12. newElements[i] = elements[i];
  13. }
  14. // 引用新数组
  15. elements = newElements;
  16. }
  17. 复制代码
  • 每次删除元素后, 判断是否需要缩容

  
  1. public E remove(int index) {
  2. // 判断索引是否越界
  3. rangeCheck(index);
  4. // 取出需要删除的元素
  5. E element = elements[index];
  6. // 通过循环, 将index后面的所有元素向前移动一位
  7. for (int i = index; i < size - 1; i++) {
  8. elements[i] = elements[i + 1];
  9. }
  10. // 删除最后一个元素
  11. elements[--size] = null;
  12. // 判断数组是否需要缩容
  13. trim();
  14. // 将删除的元素返回
  15. return element;
  16. }
  17. 复制代码

5、清空元素

  • 清空元素, 只需要将所有的元素置为null, 然后size置为0

  
  1. public void clear() {
  2. // 清空存储的数据
  3. for (int i = 0; i < size; i++) {
  4. elements[i] = null;
  5. }
  6. // 将size置为0
  7. size = 0;
  8. }
  9. 复制代码

6、修改元素

  • 修改元素时, 只需要将原有位置的元素替换掉即可, 只是需要注意一下索引是否越界

  
  1. public E set(int index, E element) {
  2. // 判断索引是否越界
  3. rangeCheck(index);
  4. // 取出被替换元素
  5. E oldElement = elements[index];
  6. // 替换元素
  7. elements[index] = element;
  8. // 返回被替换元素
  9. return oldElement;
  10. }
  11. 复制代码

7、 查询元素

  • 查询元素, 只需要将指定索引的元素返回, 注意索引是否越界即可

  
  1. public E get(int index) {
  2. rangeCheck(index);
  3. return elements[index];
  4. }
  5. 复制代码

8、插入元素

  • 插入元素类似于删除元素, 只需要将插入位置后面的元素向后移动即可
  • 注意: 需要从后向前移动元素, 如果从前向后移动元素, 那么会进行元素覆盖, 最后出错

 

 

 


  
  1. public void add(int index, E element) {
  2. // 从后向前的顺序, 将元素向后移动
  3. for (int i = size - 1; i >= index; i--) {
  4. elements[i + 1] = elements[i];
  5. }
  6. // 插入元素
  7. elements[index] = element;
  8. // size+1
  9. size++;
  10. }
  11. 复制代码
  • 需要注意的是, 插入元素的索引也不能越界, 不过不同于删除和设置元素时, 插入的位置可以是所有元素的最后, 即size的位置

  
  1. public void rangeCheckForAdd(int index) {
  2. // 当索引小于0 或 大于 size时 抛出异常
  3. if (index < 0 || index > size) {
  4. throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
  5. }
  6. }
  7. 复制代码
  • 此时, add方法如下

  
  1. public void add(int index, E element) {
  2. // 检查索引是否越界
  3. rangeCheckForAdd(index);
  4. // 从后向前的顺序, 将元素向后移动
  5. for (int i = size - 1; i >= index; i--) {
  6. elements[i + 1] = elements[i];
  7. }
  8. // 插入元素
  9. elements[index] = element;
  10. // size+1
  11. size++;
  12. }
  13. 复制代码

9、查看元素位置

  • 可以通过循环, 查找元素在数组中的位置
  • 注意: 数组中可以存储null, 而null是不能调用equals方法的, 所以需要对传入的元素进行判断, 如果查找的元素是null, 需要单独处理
  • 当元素存在时返回索引, 否则返回变量ELEMENT_ON_FOUND的值

  
  1. private static final int ELEMENT_ON_FOUND = -1;
  2. public int indexOf(E element) {
  3. if (element == null) {
  4. for (int i = 0; i < size; i++) {
  5. if (elements[i] == null) return i;
  6. }
  7. }else {
  8. for (int i = 0; i < size; i++) {
  9. if (element.equals(elements[i])) return i;
  10. }
  11. }
  12. return ELEMENT_ON_FOUND;
  13. }
  14. 复制代码

10、是否包含某个元素

  • 当元素存在时, 只需要判断索引是否等于ELEMENT_ON_FOUND即可

  
  1. public boolean contains(E element) {
  2. // 查看元素的索引是否为ELEMENT_ON_FOUND即可
  3. return indexOf(element) != ELEMENT_ON_FOUND;
  4. }
  5. 复制代码

11、元素的数量

  • 数组元素的数量, 就是size的值

  
  1. public int size() {
  2. return size;
  3. }
  4. 复制代码

12、数组是否为空

  • 判断size的值是否为0即可

  
  1. public boolean isEmpty() {
  2. return size == 0;
  3. }
  4. 复制代码

13、动态数组打印

  • 可以重写toString方法, 来打印ArrayList中的元素

  
  1. @Override
  2. public String toString() {
  3. StringBuilder string = new StringBuilder();
  4. string.append("size = ").append(size).append(", [");
  5. for (int i = 0; i < size; i++) {
  6. if (i != 0) {
  7. string.append(",");
  8. }
  9. string.append(elements[i]);
  10. }
  11. string.append("]");
  12. return string.toString();
  13. }

文章来源: javaedge.blog.csdn.net,作者:JavaEdge.,版权归原作者所有,如需转载,请联系作者。

原文链接:javaedge.blog.csdn.net/article/details/104489125

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。