数组中只能存放同一种类型的数据
数组定义:1、只设定长度 2、不设定长度,但是直接给数组赋值
int[] arr = new int[10];
int[] scores = new int[]{100,99,98};
1、 定义数组类Array
2、 向数组中添加元素,最后位置添加和指定位置 添加、起始位置添加
3、 查询数组中的元素和修改元素
先重写toString()方法,main方法中测试该方法
4、 数组中的包含、搜索和删除指定索引、删除数组第一个元素、删除数组最后一个元素、删除指定元素(按元素值删除,之前的都是按索引位置删除)
package array;
//定义数组类
public class Array {
//存放数组数据
private int[] data;
//数组中存放的元素个数,且需要明白size总是指向的待添加元素的位置
private int size;
//定义数组的容量
public Array(int capacity){
data = new int[capacity];
//初始的元素个数为0
size = 0;
}
//如果未定义容量,则使用默认容量
public Array(){
this(10);
}
//提供方法获数组元素个数
public int getSize(){
return size;
}
//获取数组的容量
public int getCapacity(){
return data.length;
}
//判断数组是否为空
public boolean isEmpty(){
return size==0;
}
//向数组末尾添加元素
public void addLast(int e){
/*//如果数组元素个数等于数组长度,则不能再增加,抛异常,后期会讲解扩容
if(size == data.length){
throw new
IllegalArgumentException("add failed! Array is full.");
}
data[size] = e;
size++;*/
//由上边的写法改为复用add方法
add(size,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! Required index>=0 and
index<=size.");
}
//向后移动元素、指定位置插入元素、size++
for(int i=size-1;i>=index;i--){
data[i+1]=data[i];
}
data[index] = e;
size++;
}
//向数组开头加入元素
public void addFirst(int e){
add(0,e);
}
//获取index索引位置的元素
public int get(int index){
if (index<0 || index >=size){
throw new IllegalArgumentException("get failed!index is illegal.");
}
return data[index];
}
//更改索引位置的元素为指定元素
public void set(int index,int e){
if (index<0 || index >=size){
throw new IllegalArgumentException("get failed!index is illegal.");
}
data[index] = e;
}
//判断数组中是否包含指定元素
public boolean contains(int e){
for(int i=0;i<size;i++){
if(data[i] == e){
return true;
}
}
return false;
}
//查找元素在数组中的位置
public int find(int e){
for(int i=0;i<size;i++){
if(data[i] == e){
return i;
}
}
//如果元素在数组中不存在,返回-1
return -1;
}
//删除指定索引位置的元素,并返回该元素值
public int remove(int index){
//判断索引位置是否合法、保存删除位置的元素值做为返回值,元素前移、size--、返回元素
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);
}
//删除数组中U最后一个元素
public int removeLast(){
return remove(size-1);
}
//根据元素值删除数组中的元素
public void removeElement(int e){
int index = find(e);
//元素在数组中存在才进行删除操作
if(index != -1){
remove(index);
}
}
//删除数组中第一个位置的元素
//删除数组中最后一个位置的元素
//根据元素值删除元素
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append(String.format("Array:size = %d ,capacity = %d \n",size,data.length));
sb.append("[");
for(int i=0;i<size;i++){
sb.append(data[i]);
if(i!=size-1){
sb.append(",");
}
}
sb.append("]");
return sb.toString();
}
}
package array; public class Main { public static void main(String[] args) { //测试自定义Array类的addLast和toString方法 Array arr = new Array(20); for(int i=0;i<10;i++){ arr.addLast(i); } System.out.println(arr.toString()); //测试add方法 arr.add(1,200); System.out.println(arr.toString()); //测试addFirst方法 arr.addFirst(-1); System.out.println(arr.toString()); boolean flag = arr.contains(8); System.out.println("测试是否包含8:"+flag); arr.remove(5); System.out.println(arr.toString()); arr.removeFirst(); System.out.println(arr.toString()); arr.removeLast(); System.out.println(arr.toString()); arr.removeElement(200); System.out.println(arr.toString()); } }
5、 泛型在数组中的应用
以上设计的数组最大的问题,就是只能存放int类型的数据,而实际中数组做为容器应该能够存放任意类型的数据
改为泛型时需要注意事项:
1) 创建数组对象的方式
2) contains()、find等方法中做比较的时候,不应该再用==引用比较,而应该使用equals进行值比较
3) 删除指定位置元素时,由于后边的元素前移,且size—后,最后size所指向的位置,实际上还保留着之前的最后一个元素,导致无法进行垃圾回收,应该手动将该值设置为null;
改后的Array类编码如下:package array;
//定义数组类 public class Array<E> { //存放数组数据 private E[] data; //数组中存放的元素个数,且需要明白size总是指向的待添加元素的位置 private int size; //定义数组的容量 public Array(int capacity){ //data = new E[capacity]; 这种写法语法上是不对的,应该写成如下写法 data = (E[])new Object[capacity]; //初始的元素个数为0 size = 0; } //如果未定义容量,则使用默认容量 public Array(){ this(10); } //提供方法获数组元素个数 public int getSize(){ return size; } //获取数组的容量 public int getCapacity(){ return data.length; } //判断数组是否为空 public boolean isEmpty(){ return size==0; } //向数组末尾添加元素 public void addLast(E e){ /*//如果数组元素个数等于数组长度,则不能再增加,抛异常,后期会讲解扩容 if(size == data.length){ throw new IllegalArgumentException("add failed! Array is full."); } data[size] = e; size++;*/ //由上边的写法改为复用add方法 add(size,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! Required index>=0 and index<=size."); } //向后移动元素、指定位置插入元素、size++ for(int i=size-1;i>=index;i--){ data[i+1]=data[i]; } data[index] = e; size++; } //向数组开头加入元素 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]; }
//获取最后一个元素 public E getLast(){ return get(size-1); } //获取第一个元素 public E getFirst(){ return get(0); }
//更改索引位置的元素为指定元素 public void set(int index,E e){ if (index<0 || index >=size){ throw new IllegalArgumentException("get failed!index is illegal."); } data[index] = e; } //判断数组中是否包含指定元素 public boolean contains(E e){ for(int i=0;i<size;i++){ if(data[i].equals(e)){ return true; } } return false; } //查找元素在数组中的位置 public int find(E e){ for(int i=0;i<size;i++){ if(data[i].equals(e)){ return i; } } //如果元素在数组中不存在,返回-1 return -1; } //删除指定索引位置的元素,并返回该元素值 public E remove(int index){ //判断索引位置是否合法、保存删除位置的元素值做为返回值,元素前移、size--、返回元素 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; return ret; } //删除数组中的第一个元素 public E removeFirst(){ return remove(0); } //删除数组中U最后一个元素 public E removeLast(){ return remove(size-1); } //根据元素值删除数组中的元素 public void removeElement(E e){ int index = find(e); //元素在数组中存在才进行删除操作 if(index != -1){ remove(index); } } //删除数组中第一个位置的元素 //删除数组中最后一个位置的元素 //根据元素值删除元素 @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append(String.format("Array:size = %d ,capacity = %d \n",size,data.length)); sb.append("["); for(int i=0;i<size;i++){ sb.append(data[i]); if(i!=size-1){ sb.append(","); } } sb.append("]"); return sb.toString(); } }
4) 在测试的时候,即在使用Array类创建对象的时候,需要用<>指定存放的具体数据类型,其他内容都不用动
//也可以写成Array<Integer> arr = new Array(20); Array<Integer> arr = new Array<Integer>(20);
5) 编写自定义类,并测试Array类中存储自定义类
package array; public class Employee { private String name; private int age; public Employee(String name,int age){ this.name=name; this.age=age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Employee{" + "name=‘" + name + ‘\‘‘ + ", age=" + age + ‘}‘; } public static void main(String[] args) { //不指定长度,Array类中定义的默认10 Array<Employee> emps = new Array<>(); emps.addLast(new Employee("zhangsan",18)); emps.addLast(new Employee("lisi",20)); emps.addLast(new Employee("wangwu",35)); System.out.println(emps); } }
6、 动态数组
以上定义的代码使用的是静态数组,而我们实际工作中,有些时候是不能预估数组需要存放数据的多少的,因此使用动态数组解决该问题
1) 定义私有的扩容方法resize(int newCapacity)
思想:定义新的数组,将旧数组的内容循环遍历拷贝进新的数组,原数组的声明变量指向新的数组
2) 新增元素和删除元素的方法中增加判断的方法,新增时当元素个数达到数组的长度(容量)时,调用resize方法进行扩容;删除元素时,当元素个数为数组容量的一半时,调用resize方法进行缩容
精髓就是定义新的数组,使原有数组指向新的数组,同时会放弃原有数组的引用
resize方法如下:
//数组动态扩容 public void resize(int newCapacity){ E[] newData = (E[])new Object[newCapacity]; for(int i=0;i<size;i++){ newData[i] = data[i]; } //使data数组指向新的引用 data = newData; }
add方法如下:
//向数组任意位置加入元素 public void add(int index,E e){ //判断欲插入的元素的下标位置是否正确 if(index<0 || index > size){ throw new IllegalArgumentException("add failed! Required index>=0 and index<=size."); } //如果数组元素个数等于数组长度,则进行动态扩容,但是扩容涉及到数组中元素的循环复制,要考虑效率 if(size == data.length){ resize(2 * data.length); } //向后移动元素、指定位置插入元素、size++ for(int i=size-1;i>=index;i--){ data[i+1]=data[i]; } data[index] = e; size++; }
remove方法如下:
//删除指定索引位置的元素,并返回该元素值 public E remove(int index){ //判断索引位置是否合法、保存删除位置的元素值做为返回值,元素前移、size--、返回元素 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; //当元素个数为数组容量的一半时进行动态缩容 if(size == data.length/2){ resize(data.length/2); } return ret; }
7、 时间复杂度分析
O(1)、O(n)、O(lgn)、O(nlogn)、O(n^2)
用大O描述的是算法的运行时间和输入数据之间的关系
为什么要用大O,叫做O(n):忽略常数,实际时间T=c1*n+c2
如T=2*n+3 和T=10000*n + 20000时间复杂度都是O(n),都是线性时间的算法
T=2*n*n+3 和T=1*n*n +300*n +20的时间复杂度都是O(n^2)
addLast(e): O(1)
addFirst(e):O(n)
add(index,e):O(n/2)=O(n),1/2是常数,所以O(n/2)=O(n)
所以添加添加整体上是O(n)的时间复杂度,即算法分析,我们主要关心的是最坏的情况下的时间复杂度。
删除操作时间复杂度O(n)
修改操作时间负责度O(1)
查找:get(index)时间复杂度O(1),contains(e)时间复杂度O(n),find(e)时间复杂度O(n)
总结:
增加时间复杂度O(n),
删除时间复杂度O(n),
修改:已知索引时间复杂度O(1),未知索引时间复杂度O(n)
查询:已知索引时间复杂度O(1),未知索引时间复杂度O(n)
8、 均摊复杂度和防止复杂度震荡
增加最后一个元素和删除最后一个元素,简单看是O(1)的,但是如果考虑到resize,而resize是需要复制一个数组的元素给另外一个数组的,所以是O(n)级别的,但是这种情况使用最坏的时间复杂度分析是不合理的,因为必须满足扩容或者缩容的情况下才会进行resize,这种概率是极低的,不应该用这种分析,而是用均摊时间复杂度,n次操作导致一次resize,而一次resize会移动n次并插入1次,相当于插入n次共做了2n+1次基本操作,相当于做一次插入需要2次的基本操作,所以addLast(e)与数组中元素的个数n并无关系,所以实际上还是O(1)时间复杂度的。
复杂度震荡:达到扩容的条件,进行添加,会进行扩容,而添加完又进行删除,此时会进行缩容,即在扩容缩容临界点反复进行增加和删除,每次都会的时间复杂度都是O(n)
解决办法:不要过于着急的缩容,即不是在数组中元素个数为容量的1/2时就进行缩容,而是等到数组中元素个数为容量的1/4时再进行缩容,且缩容为数组容量的1/2,留出空闲空间给新增元素时使用,防止新增就扩容
修改remove(index)方法中缩容部分的代码为如下:
//当元素个数为数组容量的一半时进行动态缩容 /*if(size == data.length/2){ resize(data.length/2); }*/ //由元素个数为1/2容量时就进行缩容,改为元素个数为1/4容量时才进行缩容, //且需要保证1/2容量时数组容量不等于0,因为创建数组的长度是不能为0的 if(size == data.length/4 && data.length/2!=0){ resize(data.length/2); } 如有理解不到之处,望沟通指正!
原文地址:https://www.cnblogs.com/xiao1572662/p/12090869.html