动态数组

API介绍

数组是一种根据下标操作的数据结构,它的查询速度很快,但是它有缺点,那就是数组的容量一旦在创建时确定,就不能进行更改,所以为了克服这一缺点,我们实现一个自己的数组,并除此以外,还会实现一些方法,包括以下

  • add(int index, E e)
    • 向指定index添加元素e
  • get(int index)
    • 获得指定index的元素
  • remove(int index)
    • 删除指定index的元素并返回该元素
  • set(int index, E e)
    • 更改index处的元素为e
  • getSize()
    • 返回数组中元素的个数
  • contains(E e)
    • 查询数组是否包含元素e
  • isEmpty()
    • 查看数组是否为空(是否有元素)
  • find(E e)
    • 返回数组中元素e第一次出现的index,若没有元素e,则返回-1

新建一个Array类,它含有两个私有成员变量

  • E[] data
    • 用以保存数据
  • int size
    • 用以记录数组中元素的个数

除此以外还有两个构造方法

  • Array(int capacity)
    • 设定数组的容量
  • Array()
    • 容量默认为10
  1. public class Array<E> {
  2. private E[] data;
  3. private int size;
  4. public Array(int capacity) {
  5. data = (E[]) new Object[capacity];
  6. size = 0;
  7. }
  8. public Array() {
  9. this(10);
  10. }
  11. }

现在我们来实现上面提到的方法。

方法实现

首先来实现getSize()方法,这个是返回数组元素的个数的,我们直接返回size即可

  1. public int getSize() {
  2. return size;
  3. }

isEmpty()是为了查看数组中是否还有元素,如果size为0的话说明数组为空,所以我们返回size == 0即可

  1. public boolean isEmpty() {
  2. return size == 0;
  3. }

现在来实现add(int index, E e)方法,该方法的实现是将index后面的元素都向后移动一位,然后在index处插入元素e
动态数组 - 图1

  1. public void add(int index, E e) {
  2. //对inex进行验证 如果不符合规范则抛出异常
  3. if (index < 0 || index > size) {
  4. throw new IllegalArgumentException("参数错误");
  5. }
  6. //将元素向后移动
  7. for (int i = size; i > index; i--) {
  8. data[i] = data[i - 1];
  9. }
  10. //在index处插入元素e
  11. data[index] = e;
  12. //数组中元素个数+1
  13. size++;
  14. }

根据这个方法,我们可以很快的实现addFirst(E e)和addLast(E e)方法,这两个方法一个是在数组头添加元素,一个是在数组的末尾添加一个元素

  1. public void addLast(E e) {
  2. //在index = size处添加元素 即在数组末尾添加一个元素
  3. add(size,e);
  4. }
  5. public void addFirst(E e) {
  6. //在index = 0处添加一个元素 即在数组头添加一个元素
  7. add(0,e);
  8. }

下面来实现remove(int index)方法,该方法是删除index处的元素,并将该元素返回,以添加的操作相反,删除是将后面的元素向前移动,覆盖掉index处的元素即可删除
动态数组 - 图2

  1. public E remove(int index) {
  2. //参数检查
  3. if (index < 0 || index >= size) {
  4. throw new IllegalArgumentException("参数错误");
  5. }
  6. //获得index处的元素用以返回
  7. E e = data[index];
  8. //将元素从后向前移一个
  9. for (int i = index; i < size - 1; i++) {
  10. data[i] = data[i+1];
  11. }
  12. //数组中元素个数-1
  13. size --;
  14. //返回删除的元素
  15. return e;
  16. }

同理,根据这个方法我们可以快速的实现removeLast()和removeFirst()方法

  1. public E removeLast() {
  2. return remove(size -1);
  3. }
  4. public E removeFirst() {
  5. return remove(0);
  6. }

我们可以添加一个删除指定元素的方法removeElement(E e),我们会遍历数组,如果发现有元素等于该元素,那么删除该元素并退出方法,所以这个方法只删除第一个元素e,并不是数组所有的元素e

  1. public void removeElement(E e) {
  2. //遍历数组
  3. for (int i = 0; i < size; i++) {
  4. //如果找到等于该元素的元素
  5. if (e.equals(data[i])) {
  6. //删除该元素
  7. remove(i);
  8. //退出方法
  9. return;
  10. }
  11. }
  12. }

下面实现contains(E e)方法,这个方法的思路同删除指定元素相似,遍历数组,如果找到元素与指定元素相同,那么返回true,如果遍历完数组还没有找到与之相等的元素,那么返回false

  1. public boolean contains(E e) {
  2. //遍历数组
  3. for (int i = 0; i < size; i++) {
  4. //如果找到元素,那么返回true
  5. if (data[i].equals(e)) {
  6. return true;
  7. }
  8. }
  9. //如果遍历完所有数组没有找到,那么返回false
  10. return false;
  11. }

find(E e)方法的实现也是遍历数组,如果找到了元素,那么返回下标,如果遍历完数组都没有找到,那么返回-1

  1. public int find(E e) {
  2. //遍历数组
  3. for (int i = 0; i < size; i++) {
  4. //找到元素则返回下标
  5. if (data[i].equals(e)) {
  6. return i;
  7. }
  8. }
  9. //如果遍历完数组都没有找到,返回-1
  10. return -1;
  11. }

下面实现get(int index)和set(int index, E e),这两个方法的实现及其简单,直接上代码

  1. public E get(int index) {
  2. if (index < 0 || index >= size) {
  3. throw new IllegalArgumentException("参数错误");
  4. }
  5. return data[index];
  6. }
  7. public void set(int index, E e) {
  8. if (index < 0 || index >= size) {
  9. throw new IllegalArgumentException("参数错误");
  10. }
  11. data[index] = e;
  12. }

我们可以根据get方法实现getLast()和getFirst()方法

  1. public E getFirst() {
  2. return get(0);
  3. }
  4. public E getLast() {
  5. return get(size - 1);
  6. }

现在我们已经实现了API中提到的所有的方法,但是我们还是没有解决数组容量固定的问题,为了解决这个问题,我们需要实现一个resize(int newCapacity),它的作用是改变数组的容量大小,这样当数组的容量不足时,我们调用该方法就可以将数组进行扩容,或者当数组中有大量空间空闲时,我们可以缩小数组的容量,代码如下

  1. private void resize(int newCapacity) {
  2. //创建一个新容量的数组
  3. E[] temp = (E[]) new Object[newCapacity];
  4. //将数组中的数据全部放入新数组中
  5. for (int i =0; i < size; i++) {
  6. temp[i] = data[i];
  7. }
  8. //改变数组指针指向
  9. data = temp;
  10. }

现在我们改变add(int index, E e)和remove(int index)方法,我们会在添加元素和删除元素时检查数组的容量,以便对数组进行扩容或者缩容

  1. public void add(int index, E e) {
  2. if (index < 0 || index > size) {
  3. throw new IllegalArgumentException("参数错误");
  4. }
  5. //如果数组容量满了 那么将数组的容量扩为原来的两倍
  6. if (size == data.length) {
  7. resize(data.length * 2);
  8. }
  9. for (int i = size; i > index; i--) {
  10. data[i] = data[i - 1];
  11. }
  12. data[index] = e;
  13. size++;
  14. }
  1. public E remove(int index) {
  2. if (index < 0 || index >= size) {
  3. throw new IllegalArgumentException("参数错误");
  4. }
  5. E e = data[index];
  6. for (int i = index; i < size - 1; i++) {
  7. data[i] = data[i+1];
  8. }
  9. size --;
  10. //如果数组中的元素个数为数组容量的1/4,那么容量变为原来的1/2
  11. //思考一下为什么是1/4 提示:复杂度震荡
  12. if (size == data.length/4) {
  13. resize(data.length/2);
  14. }
  15. return e;
  16. }

为了方便的打印Array类,我们重写toString()方法如下

  1. public String toString() {
  2. StringBuilder str = new StringBuilder();
  3. str.append("size " + size);
  4. str.append(" capacity " + data.length);
  5. str.append("\n[");
  6. for (int i = 0; i < size; i++) {
  7. if (i == size - 1) {
  8. str.append(data[i].toString());
  9. } else {
  10. str.append(data[i].toString() + ", ");
  11. }
  12. }
  13. str.append("]");
  14. return str.toString();
  15. }

至此,我们已经完全实现了Array,它的容量没有限制,并且提供了很多的方法供用户调用,我们将使用该类来实现其它的基本的数据结构。下面贴出完整的代码

  1. public class Array<E> {
  2. private E[] data;
  3. private int size;
  4. public Array(int capacity) {
  5. data = (E[]) new Object[capacity];
  6. size = 0;
  7. }
  8. public Array() {
  9. this(10);
  10. }
  11. public int getSize() {
  12. return size;
  13. }
  14. public boolean isEmpty() {
  15. return size == 0;
  16. }
  17. public void addLast(E e) {
  18. add(size,e);
  19. }
  20. public void addFirst(E e) {
  21. add(0,e);
  22. }
  23. public void add(int index, E e) {
  24. if (index < 0 || index > size) {
  25. throw new IllegalArgumentException("参数错误");
  26. }
  27. if (size == data.length) {
  28. resize(data.length * 2);
  29. }
  30. for (int i = size; i > index; i--) {
  31. data[i] = data[i - 1];
  32. }
  33. data[index] = e;
  34. size++;
  35. }
  36. public E removeLast() {
  37. return remove(size -1);
  38. }
  39. public E removeFirst() {
  40. return remove(0);
  41. }
  42. public E remove(int index) {
  43. if (index < 0 || index >= size) {
  44. throw new IllegalArgumentException("参数错误");
  45. }
  46. E e = data[index];
  47. for (int i = index; i < size - 1; i++) {
  48. data[i] = data[i+1];
  49. }
  50. size --;
  51. if (size == data.length/4) {
  52. resize(data.length/2);
  53. }
  54. return e;
  55. }
  56. public void removeElement(E e) {
  57. for (int i = 0; i < size; i++) {
  58. if (e.equals(data[i])) {
  59. remove(i);
  60. return;
  61. }
  62. }
  63. }
  64. public boolean contains(E e) {
  65. for (int i = 0; i < size; i++) {
  66. if (data[i].equals(e)) {
  67. return true;
  68. }
  69. }
  70. return false;
  71. }
  72. public int find(E e) {
  73. for (int i = 0; i < size; i++) {
  74. if (data[i].equals(e)) {
  75. return i;
  76. }
  77. }
  78. return -1;
  79. }
  80. private void resize(int newCapacity) {
  81. E[] temp = (E[]) new Object[newCapacity];
  82. for (int i =0; i < size; i++) {
  83. temp[i] = data[i];
  84. }
  85. data = temp;
  86. }
  87. public E get(int index) {
  88. if (index < 0 || index >= size) {
  89. throw new IllegalArgumentException("参数错误");
  90. }
  91. return data[index];
  92. }
  93. public E getFirst() {
  94. return get(0);
  95. }
  96. public E getLast() {
  97. return get(size - 1);
  98. }
  99. public void set(int index, E e) {
  100. if (index < 0 || index >= size) {
  101. throw new IllegalArgumentException("参数错误");
  102. }
  103. data[index] = e;
  104. }
  105. public String toString() {
  106. StringBuilder str = new StringBuilder();
  107. str.append("size " + size);
  108. str.append(" capacity " + data.length);
  109. str.append("\n[");
  110. for (int i = 0; i < size; i++) {
  111. if (i == size - 1) {
  112. str.append(data[i].toString());
  113. } else {
  114. str.append(data[i].toString() + ", ");
  115. }
  116. }
  117. str.append("]");
  118. return str.toString();
  119. }
  120. }