1.数组
《玩转数据结构》-liuyubobobo 课程笔记
数组
把数组码成一排进行存放

数组最好用于索引有语意的情况。
数组最大的优点:快速查询
二次封装自己的数组(动态数组)
我们需要实现:
- 实现数组的基本操作,包括增、删、查、改等方法;
- 使用泛型,使得数组可以放置“任何”数据类型;
- 实现动态数组,使数组的容量可伸缩;
完成数组的增删改查(静态数组)
首先我们完成数组的增删改查等基础功能
简单封装数组
/**
* 动态数组
* @author 肖晟鹏
* @email 727901974@qq.com
* @date 2021/3/4
*/
public class MyArray<T> {
private T[] data;
/**
* 容量
*/
private int capacity;
/**
* 表示数组中有多少个元素,指向第一个没有元素的位置
*/
private int size;
/**
* 构造函数,传入容量capacity
* @param capacity
*/
public MyArray(int capacity){
this.capacity=capacity;
this.size=0;
data=(T[]) new Object[capacity];
}
/**
* 无参构造函数
*/
public MyArray(){
this(10);
}
/**
* 构造函数,根据传入数组进行初始化
* @param data
*/
public MyArray(T[] data){
this.data=data;
this.capacity=data.length;
this.size=data.length;
}
/**
* 获取元素个数
* @return
*/
public int getSize(){
return size;
}
/**
* 获取数组容量
* @return
*/
public int getCapacity(){
return capacity;
}
/**
* 判断数组是否为空
* @return
*/
public boolean isEmpty(){
return size==0;
}
/**
* 在最后一个位置添加元素
* @param item
*/
public void addLast(T item) {
if (this.size == this.capacity) {
throw new IllegalArgumentException("AddLast failed.MyArray is full.");
}
this.data[size] = item;
this.size++;
}
/**
* index要大于0并且小于size
* @param index 索引
*/
public void checkIndex(int index){
if (index < 0 || index > size) {
throw new IllegalArgumentException("Index is illegal.Require index >= 0 and index < size.");
}
}
@Override
public String toString() {
StringBuilder res=new StringBuilder();
//%d是一个占位符
res.append("MyArray:size="+this.size+",capacity="+this.capacity+"\n");
res.append("[");
for(int i=0;i<size;i++){
res.append(this.data[i].toString());
if(i!=size-1){
res.append(",");
}
}
res.append("]");
return res.toString();
}
}
在指定位置插入元素

如果我们想在数组索引为1的位置插入一个元素(数组size>1),我们怎么做呢?
我们可以把数组索引为1和之后的元素向后挪一个位置,把位置腾出来,然后再把元素插入进去。
在具体挪的时候,要从后往前挪。如图,将100移到size指向的位置,再移动99...
移动之后,维护size(size++)
/**
* 在最后一个位置添加元素
* O(1)
* @param item
*/
public void addLast(T item) {
add(size,item);
}
/**
* 在第一个位置添加元素
* O(n)
* @param item
*/
public void addFirst(T item){
add(0,item);
}
/**
* 在指定地方插入元素
* 因为不知道具体index的数量,所以视为平均数2/n
* 因为计算大O的时候,需要忽略常数
* O(n/2) = O(n)
* @param index
* @param item
*/
public void add(int index, T item) {
//index要大于0并且小于size,因为元素要紧密相连
this.checkIndex(index);
if (this.size == this.capacity) {
throw new IllegalArgumentException("Add failed.MyArray is full.");
}
for (int i = size - 1; i >= index; i--) {
this.data[i + 1] = this.data[i];
}
this.data[index] = item;
size++;
}

修改元素
/**
* 获取元素
* @param index
* @return
*/
public T get(int index) {
this.checkIndex(index);
return this.data[index];
}
/**
* 修改元素
* @param index
* @param item
*/
public void set(int index, T item) {
this.checkIndex(index);
this.data[index] = item;
}
包含和搜索
将数组进行遍历,如果找到了,就return一个指定的值
/**
* 是否包含某元素
* O(n)
* @param item
* @return
*/
public boolean contains(T item) {
for (T i : this.data) {
if (Objects.equals(item, i)) {
return true;
}
}
return false;
}
/**
* 查找元素所在索引,若没有找到则返回-1
* O(n)
* @param item
* @return
*/
public int find(T item) {
for (int i = 0; i < size; i++) {
if (Objects.equals(item, this.data[i])) {
return i;
}
}
return -1;
}
其中Objects.equals是jdk的工具类里的方法:
package java.util;
public final class Objects {
...
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
...
}

删除元素

删除时,移动元素,从前面到后面,将索引为2的赋值给索引1,然后再将3赋值给2,这样一直到size-1(最后一个元素),最后维护size,size--。

问题:size是表示数组中有多少个元素,指向第一个没有元素的位置,但是以上这样有影响吗?
答:没有,因为用户无法获得索引为size的元素,因为我们进行了合法性判断,index必须大于等于0并且小于size的。其实在Java语言中,数组只要开辟了空间,辣么都是有默认值的。
/**
* 删除指定位置的元素
* O(n/2) = O(n)
* @param index
* @return 返回删除的元素
*/
public T remove(int index) {
//index 合法性判断
checkIndex(index);
T res = this.data[index];
for (int i = index + 1; i < size; i++) {
this.data[i - 1] = data[i];
}
size--;
//将引用置空,好让垃圾回收机制发生
data[size] = null;
return res;
}
/**
* 删除第一个元素
* O(n)
* @return
*/
public T removeFirst() {
return remove(0);
}
/**
* 删除最后一个元素
* O(1)
* @return
*/
public T removeLast() {
return remove(size - 1);
}
/**
* 删除已有的元素,包括重复的元素
* @param item
* @return 进行了删除操作返回true,没有则返回false
*/
public boolean removeElement(T item) {
boolean falg=true;
boolean res=false;
int index;
while (falg){
index=find(item);
if (index == -1) {
falg=false;
}else {
remove(index);
res=true;
}
}
return res;
}

动态数组
以上我们都还是使用的是JAVA的静态数组,不能满足我们的需求,我们需要一个可伸缩的数组。
原理:创建一个新的更大的数组,将原来的数组装入新数组

将data中的所有成员放到newData中,并且将data指向新的数组。原数组因为没有被引用了,所以会被Java的自动回收机制回收,newData在方法结束后就失效了,最终我们会得到一个更大的data

/**
* 对数组进行扩容,用户不需要知道怎么扩容或者缩容的,所以我们这里设置为private
* @param newCapacity
*/
private void resize(int newCapacity){
T[] newData=(T[]) new Object[newCapacity];
for (int i=0;i<size;i++){
newData[i]=data[i];
}
this.data = newData;
this.capacity = newCapacity;
}
我们到这里就可以思考一下,之前我们添加元素的时候,当元素装满了我们就抛出异常,辣么我们现在是不是就可以在其内部进行扩容了呢?如下:
/**
* 缩容比例
*/
private final int RESIZE_SHRINK = 2;
/**
* 判断缩容的条件
*/
private final int RESIZE_IS_SHRINK = 4;
/**
* 扩容比例
*/
private final int RESIZE_EXPANSION = 2;
...
public void add(int index, T item) {
//index要大于0并且小于size,因为元素要紧密相连
this.checkIndex(index);
//动态扩容
//使用定义的常量,避免魔法值
if (this.size == this.capacity) {
resize(this.RESIZE_EXPANSION * capacity);
}
...
}
...
public T remove(int index) {
//index 合法性判断
checkIndex(index);
//使用Lazy策略,防止复杂度震荡
//使用定义的常量,避免魔法值
if(size <= capacity/this.RESIZE_IS_SHRINK && capacity/this.RESIZE_SHRINK !=0){
resize(capacity/this.RESIZE_SHRINK);
}
...
}
注意,这里删除的时候使用了Lazy策略,这里是为了解决复杂度震荡的问题,即:数组在临界状态下,反复扩容和缩容。
如果数组缩容条件是size <= capacity/2 && capacity/2 !=0 ,有一个数组,其capacity=10,size=9,当增加一个元素的时候,进行扩容,此时其属性为capacity=20,size=10,此时只要进行删除元素,则就会进行缩容。当这个数组反复在size=10和size=9得临界情况下摩擦的时候,数组就会反复扩容缩容,这明显降低了数组的性能,所以我们设计,当size <= capacity/4 && capacity/2 !=0的时候进行缩容,即只有当数组size为capacity的4分之一的时候才缩容,并且只缩容一半,这样就避免了其在临界情况下反复扩容缩容的问题。

增加操作:
均摊复杂度:O(n)
最坏复杂度:O(n^2)
但是一般来说,扩容操作绝对不会每次都触发,所以这个时候,计算均摊复杂度更有意义

MyArray
import java.util.Objects;
/**
* 动态数组
* @author 肖晟鹏
* @email 727901974@qq.com
* @date 2021/3/4
*/
public class MyArray<T> {
/**
* 缩容比例
*/
private final int RESIZE_SHRINK = 2;
/**
* 判断缩容的条件
*/
private final int RESIZE_IS_SHRINK = 4;
/**
* 扩容比例
*/
private final int RESIZE_EXPANSION = 2;
private T[] data;
/**
* 容量
*/
private int capacity;
/**
* 表示数组中有多少个元素,指向第一个没有元素的位置
*/
private int size;
/**
* 构造函数,传入容量capacity
* @param capacity
*/
public MyArray(int capacity){
this.capacity=capacity;
this.size=0;
data=(T[]) new Object[capacity];
}
/**
* 无参构造函数
*/
public MyArray(){
this(10);
}
/**
* 构造函数,根据传入数组进行初始化
* @param data
*/
public MyArray(T[] data){
this.data=data;
this.capacity=data.length;
this.size=data.length;
}
/**
* 获取元素个数
* @return
*/
public int getSize(){
return size;
}
/**
* 获取数组容量
* @return
*/
public int getCapacity(){
return capacity;
}
/**
* 判断数组是否为空
* @return
*/
public boolean isEmpty(){
return size==0;
}
/**
* index要大于0并且小于size
* @param index 索引
*/
public void checkIndex(int index){
if (index < 0 || index > size) {
throw new IllegalArgumentException("Index is illegal.Require index >= 0 and index < size.");
}
}
/**
* 对数组进行扩容,用户不需要知道怎么扩容或者缩容的,所以我们这里设置为private
* @param newCapacity
*/
private void resize(int newCapacity){
T[] newData=(T[]) new Object[newCapacity];
for (int i=0;i<size;i++){
newData[i]=data[i];
}
this.data = newData;
this.capacity = newCapacity;
}
//============================增加元素=============================
/**
* 在最后一个位置添加元素
* O(1)
* @param item
*/
public void addLast(T item) {
add(size,item);
}
/**
* 在第一个位置添加元素
* O(n)
* @param item
*/
public void addFirst(T item){
add(0,item);
}
/**
* 在指定地方插入元素
* 因为不知道具体index的数量,所以视为平均数2/n
* 因为计算大O的时候,需要忽略常数
* O(n/2) = O(n)
* @param index
* @param item
*/
public void add(int index, T item) {
//index要大于0并且小于size,因为元素要紧密相连
this.checkIndex(index);
//动态扩容
//使用定义的常量,避免魔法值
if (this.size == this.capacity) {
resize(this.RESIZE_EXPANSION * capacity);
}
for (int i = size - 1; i >= index; i--) {
this.data[i + 1] = this.data[i];
}
this.data[index] = item;
size++;
}
//============================增加元素END==========================
//============================修改元素=============================
/**
* 获取元素
* @param index
* @return
*/
public T get(int index) {
this.checkIndex(index);
return this.data[index];
}
/**
* 修改元素
* @param index
* @param item
*/
public void set(int index, T item) {
this.checkIndex(index);
this.data[index] = item;
}
//============================增加元素END==========================
//============================查找元素=============================
/**
* 是否包含某元素
* O(n)
* @param item
* @return
*/
public boolean contains(T item) {
for (T i : this.data) {
if (Objects.equals(item, i)) {
return true;
}
}
return false;
}
/**
* 查找元素所在索引,若没有找到则返回-1
* O(n)
* @param item
* @return
*/
public int find(T item) {
for (int i = 0; i < size; i++) {
if (Objects.equals(item, this.data[i])) {
return i;
}
}
return -1;
}
//============================查找元素END==========================
//============================删除元素=============================
/**
* 删除指定位置的元素
* O(n/2) = O(n)
* @param index
* @return 返回删除的元素
*/
public T remove(int index) {
//index 合法性判断
checkIndex(index);
//使用Lazy策略,防止复杂度震荡
//使用定义的常量,避免魔法值
if(size <= capacity/this.RESIZE_IS_SHRINK && capacity/this.RESIZE_SHRINK !=0){
resize(capacity/this.RESIZE_SHRINK);
}
T res = this.data[index];
for (int i = index + 1; i < size; i++) {
this.data[i - 1] = data[i];
}
size--;
//将引用置空,好让垃圾回收机制发生
data[size] = null;
return res;
}
/**
* 删除第一个元素
* O(n)
* @return
*/
public T removeFirst() {
return remove(0);
}
/**
* 删除最后一个元素
* O(1)
* @return
*/
public T removeLast() {
return remove(size - 1);
}
/**
* 删除已有的元素,包括重复的元素
* @param item
* @return 进行了删除操作返回true,没有则返回false
*/
public boolean removeElement(T item) {
boolean falg=true;
boolean res=false;
int index;
while (falg){
index=find(item);
if (index == -1) {
falg=false;
}else {
remove(index);
res=true;
}
}
return res;
}
//============================删除元素END==========================
@Override
public String toString() {
StringBuilder res=new StringBuilder();
//%d是一个占位符
res.append("MyArray:size="+this.size+",capacity="+this.capacity+"\n");
res.append("[");
for(int i=0;i<size;i++){
res.append(this.data[i].toString());
if(i!=size-1){
res.append(",");
}
}
res.append("]");
return res.toString();
}
}

浙公网安备 33010602011771号