写在前面
今天我们来学习第一个数据结构–数组,数组里面有很多值得我们去深度挖掘的东西,因此我们首先来介绍它。
数组基础介绍
数组把数据码成一排进行存放,其根据索引号来对元素实现位置上的确定。数组中的索引号从0开始,而且Java规定数组必须存放同一类型的元素,你可以通过中括号下标的方式来获取元素。
贴士:你可以点击点击右边的小齿轮,然后点击出现的show members就可以显示所有类内部的目录
下面是常见的数组创建和遍历输出的方法:
package com.suanfa.test.Array; import java.util.Arrays; public class ArrayTest { //必须传入数字 public static void main(String[] args) { int [] arr =new int[10]; for(int i=0;i< arr.length;i++){ arr[i]=i; } System.out.println("**********************"); int [] scores =new int[]{100,95,96}; for(int j =0;j<scores.length;j++){ System.out.println(scores[j]); } System.out.println("*********************************"); for(int score:scores){ System.out.println(score); } System.out.println("*******************************"); Integer [] tests =new Integer[]{100,95,96}; Arrays.asList(tests).forEach(test -> System.out.println(test)); //使用这个不能使用其基本数据类型,需要使用其对应的包装类 System.out.println("*******************************"); scores[0]=89; for(int score:scores){ System.out.println(score); } } }
数组最大的优点就是可以快速查询,因此非常适合索引号有实际寓意的场合。
但是并非所有有语意的索引都适用于数组,如身份证号: 3101031985121 88888如果让它为索引,这得开辟很大的内存空间,造成空间浪费。
当然数组也可以处理“索引没有语意”的情况,我们这里主要处理“索引没有语意”的情况数组。你知道索引号如果没有语意,那么该如何表示没有元素呢?
注意capacity和size的区别:size是数组的长度(int类型),capacity是数组的容量(int数组类型)。
我们现在如何添加元素(超过size的情况下如何处理)?如何删除元素(挪位)? 你知道的Java数组并没有这些方法,因此需要我们基于java的数组,来进行二次封装从而实现一个属于我们自己的数组类。
但是你要明白,我们自己二次封装的是动态数组(其实依然是使用java数组来实现),而Java本身提供的数组则是静态数组,这一点需要明白。
自定义数组
假设我们定义的数组,我们需要对他们进行增删改查操作,当然并不是所有的数据结构都能进行这四个操作。再次强调一点:capacity是数组最多可以容纳元素的数量,这和实际能装元素的数量是没有关系的。
现在我们新建一个类Array,在里面写入以下代码:
package com.suanfa.test.Array;
public class Array {
private int[] data; //数组
private int size; //数组中元素的个数
/**
* 带容量参数 构造函数
*
* @param capacity 数组容量
* **/ public Array(int capacity){
data = new int[capacity];
size =0;
}
/***
* 默认的构造函数
* */ public Array(){
this(10);
}
/**
* 静态数组入参构造函数
* @param data 传入静态数组
*/ public Array(int[] data) {
this.data = data;
}
/**
* 获取数组元素个数
*
* @return size 数组元素个数
*/ public int getSize() {
return size;
}
/**
* 获取数组的容量
*
* @return capacity 获取容量
*/ public int getCapacity(){
return data.length;
}
/**
* 判断数组是否为空
*
* @return 是否为空
*/ public boolean isEmpty(){
return size==0;
}
}
向数组中添加元素
所谓的size其实就是指向数组第一个元素,而我们初始的时候是空的,因此这里size就是指向data中第一个空的位置,我们添加元素时只需要添加到arr[size],并且使用size++就可以了(因为Size表示的是数组的长度,你添加了一个元素,所以size必定需要向后面进行移位,注意添加元素的时候需要判断该数组是否具有剩余的容量。
打开刚才定义的Array类:
/** * 向所有元素末尾添加一个新元素。 * * @param e 添加的元素 */ public void addList(int e){ if(size ==data.length){ throw new IllegalArgumentException("对不起,数组容量已经满了,添加元素操作失败!"); } // data[size++]=e; //这个和下面两行代码表达的意思一样,但还是建议分开写。 data[size] =e; size++; }
上面是从元素开头进行添加元素,但是如果我们想添加元素到指定的位置呢?这又该怎么办呢?
假设我们有一个数组:int [] array = new int[] {66,88,90,100},那你肯定知道它们对应的索引号分别是0,1,2,3。现在我们希望把77这个元素插到索引号为1的位置,如何实现呢?
你可以将88及后面的元素都向后进行挪移,然后就可以把索引号1的位置给空出来了,接着把77这个元素插入即可,而且我们挪移都是从后面的元素开始挪位。相应的代码实现如下:
/** * 在第Index的位置,添加e元素 * * @param e 添加的元素,index 待添加元素的索引号 */ public void add(int index, int e){ if(size ==data.length){ throw new IllegalArgumentException("对不起,数组容量已经满了,添加元素操作失败!"); } if(index<0 || index>size){ throw new IllegalArgumentException("对不起,索引号必须在0和size之间"); } //注意数组的索引范围为0-length-1; //开始位置: size也就是最后一个元素(索引号size-1),目标位置:index(这个元素也是要挪移的) for(int i =size-1;i>=index;i--){ data[i+1]=data[i]; } //将新的元素添加到index位置处 data[index]=e; size++; }
有了这个方法,前面向数组末尾添加元素的方法就可以修改了,同时你还可以定义向数组开头添加元素的方法了:
/** * 向所有元素末尾添加一个新元素。 * * @param e 添加的元素 */ public void addLast(int e){ add(size,e); } /** * 向所有元素开头添加一个新元素。 * * @param e 添加的元素 */ public void addFirst(int e){ add(0,e); }
在数组中查询元素和修改元素
首先我们既然需要查询和修改元素,那么必须要将修改的元素进行输,因此需要重写toString方法:
/*** * 打印数组信息及遍历元素 * * @return 数组信息和元素遍历结果 * */ @Override public String toString() { StringBuilder res =new StringBuilder(); res.append(String.format("Array: size = %d, capacity = %dn",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(); //这里必须使用StringBuilder的toString方法 }
然后我们来测试一下这个输出方法的正确性,打开ArrayTest.java文件,删除原来的数组代码,新增代码如下:
Array array=new Array(20); for(int i =0;i<10;i++){ array.addList(i); } System.out.println(array); System.out.println("**************************************************"); array.add(2,100); System.out.println(array); System.out.println("**************************************************"); array.addFirst(-1); System.out.println(array); //输出结果: Array: size = 10, capacity = 20 [0,1,2,3,4,5,6,7,8,9] ************************************************** Array: size = 11, capacity = 20 [0,1,100,2,3,4,5,6,7,8,9] ************************************************** Array: size = 12, capacity = 20 [-1,0,1,100,2,3,4,5,6,7,8,9]
现在是如何实现数组的索引访问和索引修改,我们继续在Array这个类里面新增代码:
/** * 根据index来获取对应位置的元素 * * @param index 索引号 * **/ public int get(int index){ if(index<0 || index>size){ throw new IllegalArgumentException("对不起,索引号必须在0和size之间"); } return data[index]; } /** * 根据index来修改对应位置的元素 * * @param index 索引号,e 元素 * **/ public int set(int index,int e){ if(index<0 || index>size){ throw new IllegalArgumentException("对不起,索引号必须在0和size之间"); } return data[index]=e; }
完成了修改的功能,接下来我们来实现查找数组中是否包含元素e的功能:
/*** * *查找数组中是否包含元素e * * @param e 元素 * */ public boolean contains(int e){ for(int i =0;i<size;i++){ if(data[i]==e){ return true; } } return false; }
以及查找元素e在数组中的位置(也就是最后返回索引号):
/*** * 方法1 * *查找元素e在数组中的位置(索引号) * * @param e 元素 * */ public int find(int e){ //一般找不到该元素就返回-1 int i=-1; if(contains(e)==true){ for(i=0;i<size;i++){ if(data[i] ==e){ return i; } } }else{ System.out.println("数组中不存在该元素"); } return i; } /*** * 方法2 * *查找元素e在数组中的位置(索引号),一般找不到该元素就返回-1 * * @param e 元素 * */ public int find(int e){ for(int i =0;i<size;i++){ if(data[i]==e){ return i; } } return -1; }
现在我们来完成在数组中删除元素的功能。
数组中删除元素
前面我们介绍了向数组中的指定位置添加元素,那么你就可以思考一下数组中如何删除指定位置的元素,这就是一个互逆的过程,不过删除的时候,元素都是向前挪位,反过来了。相应的代码实现如下:
/** * 删除第Index位置的元素,并返回删除的元素 * * @param index 待删除元素的索引号 * * */ public int remove(int index){ if(index<0 || index>size){ throw new IllegalArgumentException("对不起,索引号必须在0和size之间"); } //注意数组的索引范围为0-length-1; //开始位置: index后面一个元素(索引号index+1),目标位置:index(这个元素也是要挪移的),然后新的元素将其进行覆盖 for(int i =index+1;i<size;i++){ data[i-1]=data[i]; //记住这里不能使用data[i+1]=data[i]; } //size每次都需要-1 size--; //将删除的元素进行返回 return data[index]; }
同样我们可以定义删除第一个,最后一个元素的方法,以方便调用:
/** * 删除数组开头的元素,并返回删除的元素 * * @param index 待删除元素的索引号 */ public int removeFirst(){ return remove(0); } /** * 删除数组末尾的元素,并返回删除的元素 * * @param index 待删除元素的索引号 */ public int removeLast(){ return remove(size-1); }
以及删除数组中某个存在的值:
/** * 删除数组中指定的元素 * * @param e 待删除的元素 */ public void removeElement(int e){ int index =find(e); //判断元素是否存在,存在的话 if(index != -1){ remove(index); } }
当然我们这里需要说明就是我们这里的查找只是返回的第一个元素的索引值,因此删除也只是第一个元素。你可以尝试查找和删除所有的元素,但是这些都是设计模式的问题,而不再是数据结构的实现要求了。当然还包括如何删除其中重复的元素,其实就是重写里面的hashCode()和equals()方法。
最后我们来测试一下前面定义的方法是不是成功的,打开ArrayTest文件,新增以下代码:
int index =array.find(4); System.out.println(index); System.out.println("**************************************************"); array.remove(3); System.out.println(array); System.out.println("**************************************************"); array.removeFirst(); System.out.println(array); System.out.println("**************************************************"); array.removeLast(); System.out.println(array); System.out.println("**************************************************"); array.removeElement(4); System.out.println(array); System.out.println("**************************************************"); //输出结果: 6 ************************************************** Array: size = 11, capacity = 20 [-1,0,1,2,3,4,5,6,7,8,9] ************************************************** Array: size = 10, capacity = 20 [0,1,2,3,4,5,6,7,8,9] ************************************************** Array: size = 9, capacity = 20 [0,1,2,3,4,5,6,7,8] ************************************************** Array: size = 8, capacity = 20 [0,1,2,3,5,6,7,8] **************************************************