0033数组结构之数组
作者:互联网
数组中只能存放同一种类型的数据
数组定义: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 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);
}
如有理解不到之处,望沟通指正!
标签:index,int,元素,0033,数组,结构,data,size 来源: https://www.cnblogs.com/xiao1572662/p/12090869.html