- 数组最大的优点:快速查询
- 数组最好应用于“索引有语意”的情况
- 但并非所有有语意的索引都适用于数组,如身份证号
- 数组也可以处理“索引没有语意”的情况
1 制作数组类
主要包含的功能有:
- 1、(增)向数组中添加元素:
add(int index, int e)
- 2、(增++)在所有元素前添加一个新元素:
addFirst(int e)
- 3、(增++)向所有元素后添加一个新元素:
addLast(int e)
- 4、(删)从数组中删除index位置的元素, 返回删除的元素:
remove(int index)
- 5、(删++)从数组中删除第一个元素, 返回删除的元素:
removeFirst()
- 6、(删++)从数组中删除最后一个元素, 返回删除的元素:
removeLast()
- 7、(删2) 从数组中删除元素e:
removeElement(int e)
- 8、(改)修改index索引位置的元素为e:
set(int index, int e)
- 9、(查)获取index索引位置的元素:
get(int index)
- 10、(查2)查找数组中元素e所在的索引,如果不存在元素e,则返回-1:
find(int e)
- 11、(查3)查找数组中是否有元素e:
contains(int e)
其他方法:
- 获取数组的容量:
getCapacity()
- 获取数组中的元素个数:
getSize()
- 返回数组是否为空:
isEmpty()
public class Array {
private int[] data;
private int size;
// 构造函数,传入数组的容量capacity构造Array
public Array(int capacity){
data = new int[capacity];
size = 0;
}
// 无参数的构造函数,默认数组的容量capacity=10
public Array(){
this(10);
}
// 获取数组的容量
public int getCapacity(){
return data.length;
}
// 获取数组中的元素个数
public int getSize(){
return size;
}
// 返回数组是否为空
public boolean isEmpty(){
return size == 0;
}
// 向所有元素后添加一个新元素
public void addLast(int e){
add(size, e);
}
// 在所有元素前添加一个新元素
public void addFirst(int e){
add(0, e);
}
// 在index索引的位置插入一个新元素e
public void add(int index, int e){
if(size == data.length)
throw new IllegalArgumentException(
"Add failed. Array is full.");
if(index < 0 || index > size)
throw new IllegalArgumentException(
"Add failed. Require index >= 0 and index <= size.");
for(int i = size - 1; i >= index ; i --)
data[i + 1] = data[i];
data[index] = e;
size ++;
}
// 获取index索引位置的元素
public int get(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException(
"Get failed. Index is illegal.");
return data[index];
}
// 修改index索引位置的元素为e
public void set(int index, int e){
if(index < 0 || index >= size)
throw new IllegalArgumentException(
"Set failed. Index is illegal.");
data[index] = e;
}
// 查找数组中是否有元素e
public boolean contains(int e){
for(int i = 0 ; i < size ; i ++){
if(data[i] == e)
return true;
}
return false;
}
// 查找数组中元素e所在的索引,如果不存在元素e,则返回-1
public int find(int e){
for(int i = 0 ; i < size ; i ++){
if(data[i] == e)
return i;
}
return -1;
}
// 从数组中删除index位置的元素, 返回删除的元素
public int remove(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException(
"Remove failed. Index is illegal.");
int ret = data[index];
for(int i = index + 1 ; i < size ; i ++)
data[i - 1] = data[i];
size --;
return ret;
}
// 从数组中删除第一个元素, 返回删除的元素
public int removeFirst(){
return remove(0);
}
// 从数组中删除最后一个元素, 返回删除的元素
public int removeLast(){
return remove(size - 1);
}
// 从数组中删除元素e
public void removeElement(int e){
int index = find(e);
if(index != -1)
remove(index);
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format(
"Array: size = %d , capacity = %d\n", size, data.length));
res.append('[');
for(int i = 0 ; i < size ; i ++){
res.append(data[i]);
if(i != size - 1)
res.append(", ");
}
res.append(']');
return res.toString();
}
}
2 使用泛型改造数组类
- 使用泛型可以让我们的数据结构可以放置任何数据类型
- 但是不能是基本数据类型,只能是类对象
- 基本数据类型:
- boolean、byte、char、short、int、long、float、double
- 每个基本数据类型都有对应的包装类:
- Boolean、Byte、Char、Short、Int、Long、Float、Double
public class Array<E> {
private E[] data;
private int size;
// 构造函数,传入数组的容量capacity构造Array
public Array(int capacity){
data = (E[])new Object[capacity];
size = 0;
}
// 无参数的构造函数,默认数组的容量capacity=10
public Array(){
this(10);
}
// 获取数组的容量
public int getCapacity(){
return data.length;
}
// 获取数组中的元素个数
public int getSize(){
return size;
}
// 返回数组是否为空
public boolean isEmpty(){
return size == 0;
}
// 在index索引的位置插入一个新元素e
public void add(int index, E e){
if(size == data.length)
throw new IllegalArgumentException(
"Add failed. Array is full.");
if(index < 0 || index > size)
throw new IllegalArgumentException(
"Add failed. Require index >= 0 and index <= size.");
for(int i = size - 1; i >= index ; i --)
data[i + 1] = data[i];
data[index] = e;
size ++;
}
// 向所有元素后添加一个新元素
public void addLast(E e){
add(size, e);
}
// 在所有元素前添加一个新元素
public void addFirst(E e){
add(0, e);
}
// 获取index索引位置的元素
public E get(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException(
"Get failed. Index is illegal.");
return data[index];
}
// 修改index索引位置的元素为e
public void set(int index, E e){
if(index < 0 || index >= size)
throw new IllegalArgumentException(
"Set failed. Index is illegal.");
data[index] = e;
}
// 查找数组中是否有元素e
public boolean contains(E e){
for(int i = 0 ; i < size ; i ++){
if(data[i].equals(e))
return true;
}
return false;
}
// 查找数组中元素e所在的索引,如果不存在元素e,则返回-1
public int find(E e){
for(int i = 0 ; i < size ; i ++){
if(data[i].equals(e))
return i;
}
return -1;
}
// 从数组中删除index位置的元素, 返回删除的元素
public E remove(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException(
"Remove failed. Index is illegal.");
E ret = data[index];
for(int i = index + 1 ; i < size ; i ++)
data[i - 1] = data[i];
size --;
data[size] = null; // loitering objects != memory leak
return ret;
}
// 从数组中删除第一个元素, 返回删除的元素
public E removeFirst(){
return remove(0);
}
// 从数组中删除最后一个元素, 返回删除的元素
public E removeLast(){
return remove(size - 1);
}
// 从数组中删除元素e
public void removeElement(E e){
int index = find(e);
if(index != -1)
remove(index);
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format(
"Array: size = %d , capacity = %d\n", size, data.length));
res.append('[');
for(int i = 0 ; i < size ; i ++){
res.append(data[i]);
if(i != size - 1)
res.append(", ");
}
res.append(']');
return res.toString();
}
}
public class Main {
public static void main(String[] args) {
Array<Integer> arr = new Array<>(20);
for(int i = 0 ; i < 10 ; i ++)
arr.addLast(i);
System.out.println(arr);
arr.add(1, 100);
System.out.println(arr);
arr.addFirst(-1);
System.out.println(arr);
// [-1, 0, 100, 1, 2, 3, 4, 5, 6, 7, 8, 9]
arr.remove(2);
System.out.println(arr);
arr.removeElement(4);
System.out.println(arr);
arr.removeFirst();
System.out.println(arr);
}
}
public class Student {
private String name;
private int score;
public Student(String studentName, int studentScore){
name = studentName;
score = studentScore;
}
@Override
public String toString(){
return String.format("Student(name: %s, score: %d)", name, score);
}
public static void main(String[] args) {
Array<Student> arr = new Array<>();
Student s1 = new Student("Alice", 100);
arr.addLast(s1);
// arr.addLast(new Student("Alice", 100));
arr.addLast(new Student("Bob", 66));
arr.addLast(new Student("Charlie", 88));
boolean bol = arr.contains(s1);
System.out.println(bol);
System.out.println(arr);
}
}
3 动态数组
在添加元素的时候,如果元素个数等于数组容量的时候,对数组进行扩容
在删除元素的时候,当数组元素等于数组容量的二分之一时候,对数组进行缩容
resize就是改变数组容量的方法
// 将数组空间的容量变成newCapacity大小
private void resize(int newCapacity){
E[] newData = (E[])new Object[newCapacity];
for(int i = 0 ; i < size ; i ++)
newData[i] = data[i];
data = newData;
}
这样就实现了动态的数组,数组会根据情况改变容量大小
4 resize的复杂度分析
假设capacity=n,n+1次addLast,触发resize(resize会进行n次基本操作),总共进行了2n+1次操作。
所以平均下来,每次addLast操作,进行了2次基本操作
所以均摊计算,addLast
的时间复杂度是O(1)
同理,removeLast
的均摊时间复杂度也为O(1)
5 复杂度震荡
有一种极端的情况,addLast后,进行resize,然后removeLast,又进行resize操作,如果一直循环进行这样的操作,addLast和removeLast的复杂度就退化为了O(n),这就出现了复杂度震荡的问题
出现问题的原因就是:removeLast时resize多于着急(Eager)
解决这个问题很简单:Lazy。
当size==capacity/4 时才将capacity减半。
6 动态数组的时间复杂度
1 添加操作
- addLast(e):O(1)
- addFirst(e):O(n)
- add(index,e):O(n/2)=O(n)
2 删除操作
- removeLast(e) : O(1)
- removeFirst(e):O(n)
- remove(index,e):O(n/2)=O(n)
3 修改操作
- set(index,e):O(1)
4 查找操作
- get(index) :O(1)
- contains(e) : O(n)
- find(e) : O(n)