一.基本实现
public class ArrayDemo {--> Array<E>
private int size;//指向当前元素
private int[] dataArr; ->E[]dataArr;//表示元素个数-
public ArrayDemo(int capacity){
dataArr = new int[capacity];-->(E[])new objcet[capacity]
size = 0;
}
//无参构造
public ArrayDemo(){
this(10);
}
//获取数组中的元素个数
public int getSize(){
return size;
}
//获取数据中的容量
public int getCapacity(){
return dataArr.length;
}
//判断数据时候为空
public boolean isEmpty(){
return size==0;
}
/**
* 添加元素
*/
//向所有数组后添加一个新元素
public void addLast(int element){--> E elemnet
/*if(size == getCapacity()){
throw new IllegalArgumentException("The array is full");
}*/
//dataArr[size++] = element;
add(size,element);
}
public void addFirst(int element){--> E elemnet
/*if(size == getCapacity()){
throw new IllegalArgumentException("The array is full");
}*/
add(0,element);
}
public void resize(int newSize){
E[] newData = (E[])new Object[newSize];
for(int i=0;i<size;i++){
newData[i] = dataArr[i];
}
dataArr = newData;
}
//向指定位置添加元素
public void add(int index,int element){--> E elemnet
if(size == getCapacity()){
//throw new IllegalArgumentException("The array is full");
resize(dataArr.length*2);//java扩容的时候选的是1.5
}
if(index >size || index< 0){
throw new IllegalArgumentException();
}
//不能使用for(int i=index;i<size;i++),这样会使得后面的数字和element完全一样
for(int i=size-1;i>=index;i--){
dataArr[i+1] = dataArr[i];
}
size++;
dataArr[index] = element;
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format("Arrays: size=%d, capacit= %d\n", size, getCapacity()));
res.append("[");
for(int i=0;i< size; i++){
res.append(dataArr[i]);
if(i!=size-1){
res.append(", ");
}
}
res.append("]");
return res.toString();
}
//获取Index索引位置的元素
public int get(int index){--> E get(int index)
if(index <0 || index >= size){
throw new IllegalArgumentException("Get failed, the index is illegal");
}
return dataArr[index];
}
//修改Index索引位置的元素
void set(int index, int element){--> E element
if(index <0 || index >= size){
throw new IllegalArgumentException("Get failed, the index is illegal");
}
dataArr[index] = element;
}
/**
* 包含/搜索/删除
* */
//
//包含
public boolean contains(int element){-->E element
for(Integer x : dataArr){-->E x
if(element == x){-->element.equal(x)
return true;
}
}
return false;
}
//查看某元素的索引
public int find(int element){-->E element
for(int i=0;i<size;i++){
if(element == dataArr[i]){-->element.equal(dataArr[i])
return i;
}
}
return -1;
}
public String findAll(int element){-->E element
StringBuilder sb = new StringBuilder();
for(int i=0;i<size;i++){
if(element == dataArr[i]){-->element.equal(dataArr[i])
sb.append(i);
}
}
if(sb.toString().isEmpty()){
return "-1";
}else{
return sb.toString();
}
}
//删除某个位置的元素
public int remove(int index){-->E remove
if(index < 0 || index >= size){
throw new IllegalArgumentException("Remove failed, the index is illegal");
}
int data = dataArr[index];
//不能使用for(int i=size-1;i>index;i--),数据会被污染,就都会变成arr[index-1]
for(int i=index+1;i<size;i++){
dataArr[i-1]= dataArr[i];
}
size--;
if(size == dataArr.length/2){
resize(dataArr.length/2);
}
return data;
}
public int removeFirst() {-->E removeFirst
return remove(0);
}
public int removeLast(){-->E removeLast
return remove(size-1);
}
public Boolean removeElement(int element){-->E element
int index = find(element);
if(index != -1){
remove(index);
return true;
}
return false;
}
public void removeAllElement(int element){-->E element
for(int i=0;i<size;i++){
if(dataArr[i]==element){-->element.equal(dataArr[i])
remove(i);
}
}
}
}
二.测试类
用自己new的类去测试
public class Student {
private String name;
private int score;
public Student(String name,int score){
this.name = name;
this.score = score;
}
@Override
public String toString(){
return String.format("Stdent(name: %s,score:%d)",name,score);
}
public static void main(String[] args) {
ArrayDemo<Student> stuArr = new ArrayDemo<Student>();
stuArr.addLast(new Student("廖明",39));
stuArr.addLast(new Student("小红",78));
stuArr.addLast(new Student("橙子",99));
System.err.println(stuArr.toString());
}
//打印结果
Arrays: size=3, capacit= 10
[Stdent(name: 廖明,score:39), Stdent(name: 小红,score:78), Stdent(name: 橙子,score:99)]
}
三.将实现类中的特定数据结构改为泛型
1. ArrayDemo 改为--> ArrayDemo<E>
这里的E表示的是“类型”
2. 将所有的要使用的数据类型都改为E
2.1 private int[] dataArr -> E[]dataArr
2.2 dataArr = new int[capacity];-->E[capacity]
但是java本身不支持直接new一个泛型数组,需要做如下变动:
dataArr = (E[])new Object[capacity];
2.3 public void add(int index,int element){--> E elemnet
2.4 public int get(int index){--> E get(int index)
3. 对于remove函数来说,根据我们的Remove逻辑来说,dataArr[size]其实还指着一个值,只不过用户访问不到。但是当使用泛型时,数据中存放的都是对象的引用,所以dataArr[size]还指着一个对象的引用,对于引用就有一个对象释放的问题,在java中有垃圾回收技术。但是这里还存着引用,就不会被java的垃圾回收器回收。
public int remove(int index){
size--;
//写上这个,原来dataArr[size]所指向的对象就已经不再程序中和任何其他对象相关联了。java的回收机制就会回收它。
//但是可以不写,因为添加新的元素之后,dataArr[size]指向了新的对象,原来的对象就每人指向它了,他就会被垃圾回收器回收。
对于这种对象叫做loitering objects != memory leak
dataArr[size] = null;//
return data;
}
4. 使用
ArrayDemo<Integer> ad = new ArrayDemo<Integer>(20);
四。使用动态数组
1. 思路
1.1 当数组满了(size=capacity)之后,开辟一个新的数组newdatta.将原来的data数组中的值依次放到新的数组newData中。将新的数组中的capacity的newsize指定为data的size*2.
1.2 将原来的data数组指向新的有size*2的数组。这时候data和newdata指向同一空间。我们将这个过程写在一个方法中,当这个方法执行完毕,newdata就会失效,但是data是整个类的成员变量,所以data有效,并且指向新的数组。
1.3 原来的数组由于没有对象指向它,java的垃圾回收器会将他回收。
2. 为什么不让capacity= 2*capacity;
因为 java中数组的长度不可以改变。 java中的数组的定义就是相同类型,固定长度的一组数据,一旦被初始化,长度不可更改。
3. 代码
public void resize(int newSize){
E[] newData = (E[])new Object[newSize];
for(int i=0;i<size;i++){
newData[i] = dataArr[i];
}
dataArr = newData;
}
//扩容
public void add(int index,E element){
if(size == getCapacity()){
resize(dataArr.length*2);//java扩容的时候选的是1.5
}
}
//缩容
public E remove(int index){size--;
dataArr[index] = null;
if(size == dataArr.length/2){
//之所以这里的代码正确,是因为每添加一个index就会+1;例如:数组中只有一个元素的时候,index为1,如果数组capacity是6,添加完6个元素后,size就变成了6.
//当删除2个元素时候size变成了4,删除3个元素的时候,size=3.这时候,刚刚占用了一半的位置。
resize(dataArr.length/2);
}
return data;
}
五。时间复杂度分析
1. 简单的时间复杂度
(1) O(1), O(n), O(nlogn), O(n^2)
(2) 大O描述的是算法的时间运行和输入数据之间的关系
例如:定义n是nums中的元素个数,运行时间的多少和元素个数成线性关系.n越大,也就是nums中的数多,运行时间就越长。这里是O(n)
public static int sum(int[] nums){
int sum=0;
for(int num:nums) sum+=num;
return sum;
}
但是实际时间T=c1*n + c2。但是不同语言的比如说将nums整个数从数组中取出来这件事,不同语言基于不同的实现,时间是不同的。就算转化成机器码,它对应的机器码的指令数也可能是不一样的。就算指令是相同的,根据cpu的不同,执行的操作也是不同的。
所以即使我们能大概说出c1是几条指令,但是很难准确说出它的值。所以忽略常数,所以就是O(n)
1. c1*n :我们需要把num从nums的数组中取出来,然后还要将sum取出来,最后我们还要将num和sum加在一起,
2. c2 : 开始前要开辟一个sum的空间,将0初始化赋给sum。在算法运行结束之后还要将sum来return回去。
(3) 由于O()是忽略常数的,所以并不代表对于任意输入来说,O(n)已经比O(n^2)越好。
(4) O() 其实就是[渐进时间复杂度],是对于n趋紧于无穷的情况。
(5) O(1) [就意味着这个操作所消耗的时间和数据规模无关。]
2. 数组中方法的时间复杂度
2.1 以下都可以归结为O(n)级别的。因为我们考虑的是最坏情况, 而且考虑到[resize操作】所以这个是 O(n)的。
addLast(e) O(1)【数组的规模就是数组中的元素个数,也就是不管数组中有多少元素,addLast都能在常数范围里完成】
addFirst(e) O(n)【需要把数组所有元素都向后挪一个单位,所以复杂度是O(n)】
add(index, e) 平均是O(n/2) = O(n)
严格计算:由于index可以取从0到size这么多种可能,但是取到每一个值的概率是相等的。这种情况下我们就可以求出它的时间期望是多少。
2.2 以下都可以归结为O(n)级别的。因为我们考虑的是最坏情况, 而且考虑到[resize操作】所以这个是 O(n)的。
removeLast(e) O(1)
removeFirst(e) O(n)
remove(index, e) O(n/2) = O(n)
2.3 搜索
set(index,e) O(1)
2.4 查询
get(index) O(1)
contians(e) O(n)
find(e) O(n)
六。 均摊时间复杂度和防止复杂度的震荡
1. resize的复杂度分析--【均摊复杂度-amortized time complexity】
1.1 假设当前capacity=8,并且每一次添加操作都使用addLast
(1) 前8次元素的添加都是O(1)的时间复杂度
(2) 当到第九个元素的时候,resize操作,添加8个数到新数组,再添加这个数,这个花费9次操作
(3) 所以9次addLast操作,总共进行了17次基本操作.平均,每次addLast操作,进行2次基本操作.
(4) 假设capacity=n,n+1次addLast,触发resize,总共进行2n+1次基本操作。平均,每次addLast操作,进行2次基本操作.
(5) 按照这样平摊来算的话,addLast的时间复杂度是O(1)级别的。
(6) 在这个例子中,这样均摊计算,比计算最坏情况有意义。
2. 复杂度震荡
2.1 同时进行addLast和removeLast操作。
(1) 如果capacity是n。并且已经装满了
(2) 然后我添加1个元素,addLast, 触发resize,容量变成2n, 复杂度为O(n)
(3) 此时再删除1个元素, removeLast, 触发resize,容量变成n, 复杂度为O(n)
(4) 重复(2)和(3)
2.2 出现问题的原因是: removeLast和resize过于着急(Eager)
2.3 解决方案: Lazy
当数组删除到了数组的1/4的时候,我们缩容1/2.所以还剩1/4的空间,这时候添加元素就不不会立即触发resize.
2.4 代码
(1) if(size == dataArr.length/2){}
变为
if(size == dataArr.length/4){}
(2) 但是此时有一个小bug,因为随着缩容,dataArr.length可能为1.也就是dataArr.length/2会为0.
(3) 但是又需要resize操作。
resize(dataArr.length/2);
这里的数组的长度不能为0.所以变成如下:
if(size == dataArr.length/4 && dataArr.length/2!=0){}