1. 线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,指具有相同数据类型的元素按照一定的顺序排列的数据结构,其中每个元素都有唯一的前驱元素和后继元素(除了第一个元素没有前驱,最后一个元素没有后继),常见的线性表:顺序表、链表、栈、队列…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2. 顺序表
顺序表是一种线性表的存储结构,它是由一组地址连续的存储单元依次存储线性表中的元素,元素之间的逻辑关系和物理关系是一致的。顺序表可以通过数组来实现,在数组上完成数据的增删查改。
顺序表的主要特点是:
- 元素之间的逻辑关系和物理关系是一致的,即顺序表中的元素在内存中是连续存储的。
- 可以通过下标直接访问和修改元素,因此支持随机访问。
- 具有固定的容量,需要预先分配足够的内存空间来存储元素,如果需要动态调整大小,可能需要重新分配内存空间并进行元素的复制。
- 元素的插入和删除操作可能需要移动其他元素来保持顺序,因此效率较低。
顺序表在实际应用中有着广泛的应用,例如数组就是一种顺序表的实现方式。在计算机科学中,顺序表也是一种重要的数据结构,用于存储和操作线性表中的元素。
3. 模拟实现顺序表
下面是我写的一个基于数组实现的顺序表(MyArrayList)的完整代码,包含了每个方法的实现:
【IList.clss】:
模拟List接口,里面记录了MyArrayList需要实现的方法
public interface IList {
// 新增元素,默认在数组最后新增
public void add(int data);
// 在 pos 位置新增元素
public void add(int pos, int data);
// 判定是否包含某个元素
public boolean contains(int toFind);
// 查找某个元素对应的位置
public int indexOf(int toFind);
// 获取 pos 位置的元素
public int get(int pos);
// 给 pos 位置的元素设为 value
public void set(int pos, int value);
//删除第一次出现的关键字key
public void remove(int toRemove);
// 获取顺序表长度
public int size();
// 清空顺序表
public void clear();
// 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
public void display();
}
【MyArrayListEmpty.clss】:
自定义异常数组为空异常(运行时异常)
public class MyArrayListEmpty extends RuntimeException{
public MyArrayListEmpty(String msg) {
super(msg);
}
}
【PositionIllegal.clss】:
自定义异常数组下标非法(运行时异常)
public class PositionIllegal extends RuntimeException{
public PositionIllegal(String msg) {
super(msg);
}
}
【PositionOutOfBounds .clss】:
自定义异常数组访问元素时下标越界(运行时异常)
public class PositionOutOfBounds extends RuntimeException {
public PositionOutOfBounds(String msg) {
super(msg);
}
}
【MyArrayList.class】:
public class MyArrayList implements IList {
int[] elements;//用来存放数据元素
int usedSize;//代表当前顺序表中的有效数据个数
public static final int DEFAULT_SIZE = 2;//默认的数组中的数据的个数
// 默认构造方法
public MyArrayList() {
this.elements = new int[DEFAULT_SIZE];//默认初始大小
}
// 将顺序表的底层容量设置为initCapacity
public MyArrayList(int initCapacity) {
this.elements = new int[initCapacity];//自定义大小
}
@Override
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elements[i] + " ");
}
System.out.println();
}
@Override
public void add(int data) {
//扩容
checkCapacity();
//添加数组
this.elements[this.usedSize] = data;
//有效数据个数加一
this.usedSize++;
}
private boolean isFull() {
//1.检查数组是否满了
if (this.usedSize == elements.length) {
return true;
}
return false;
}
private void checkCapacity() {
//2.检查数组容量并进行扩容
if (isFull()) {
// 数组容量扩大二倍
this.elements = Arrays.copyOf(elements, 2 * (this.elements.length));
}
}
@Override
public void add(int pos, int data) {
//1.检查数组元素下标是否合法
checkPositionIllegalOnAdd(pos);
//2.检查数组的容量
checkCapacity();
//3.将数据存放在对应要存放的位置
for (int i = this.usedSize - 1; i >= pos; i++) {
this.elements[i + 1] = this.elements[i];
}
this.elements[pos] = data;
this.usedSize++;
}
@Override
public boolean contains(int toFind) {
//1.判断数组是否为空
isEmpty();
//2.判断数组中是否含有该元素
for (int i = 0; i < this.usedSize; i++) {
if (this.elements[i] == toFind) {
return true;
}
}
return false;
}
@Override
public int indexOf(int toFind) {
//1.判断数组是否为空
isEmpty();
//2.判断数组中是否含有该元素
for (int i = 0; i < this.usedSize; i++) {
if (this.elements[i] == toFind) {
return i;
}
}
//3.数组中不含有该元素,返回值为-1
return -1;
}
@Override
public int get(int pos) {
//检查数组下标是否越界
checkPositionGetAndSet(pos);
//判断数组是否为空
isEmpty();
//得到下标对应的值
int ret = this.elements[pos];
return ret;
}
@Override
public void set(int pos, int value) {
//检查数组下标是否越界
checkPositionGetAndSet(pos);
//判断数组是否为空
isEmpty();
//设置下标对应的值
this.elements[pos] = value;
}
@Override
public void remove(int toRemove) {
//获取要删除的元素对应的下标
int index = indexOf(toRemove);
//判断下标
if (index != -1) {
//将index之后的所有元素向前移动一位
for (int i = index; i < this.usedSize - 1; i++) {
this.elements[i] = this.elements[i + 1];
}
//删除元素之后,数组计数器减一
this.usedSize--;
}
}
@Override
public int size() {
return this.usedSize;
}
@Override
public void clear() {
this.usedSize = 0;
}
private void checkPositionIllegalOnAdd(int pos) throws PositionIllegal {
//存放数据的合法位置为 :0 ~ usedSize
if (pos < 0 || pos > this.usedSize) {
throw new PositionIllegal("数组的下标" + pos + "不合法");
}
}
private void checkPositionGetAndSet(int pos) {
if (pos < 0 || pos >= this.usedSize) {
throw new PositionOutOfBounds("数组下标" + pos + "越界");
}
}
public void isEmpty() {
if (this.usedSize == 0) {
throw new MyArrayListEmpty("该数组为空");
}
}
}
【讲解部分一】:
private void checkCapacity() {
//2.检查数组容量并进行扩容
if (isFull()) {
// 数组容量扩大二倍
this.elements = Arrays.copyOf(elements, 2 * (this.elements.length));
}
}
Arrays.copyOf ( ) 方法是Java中用于复制数组的方法。它可以复制指定数组的所有元素到一个新数组中,并返回新数组。 Arrays.copyOf ( ) 方法有两个参数:源数组和目标数组的长度。
下面是 Arrays.copyOf ( ) 方法的语法:
public static <T> T[] copyOf(T[] original, int newLength)
其中,original是源数组,newLength是目标数组的长度。返回的数组类型与源数组类型相同。需要注意的是, Arrays.copyOf ( ) 方法会根据目标数组的长度进行复制。如果目标数组长度小于源数组长度,复制结果将截取源数组的前部分元素;如果目标数组长度大于源数组长度,复制结果将在末尾填充默认值 0 。
【讲解部分二】:
public boolean contains(int toFind) {
//1.判断数组是否为空
isEmpty();
//2.判断数组中是否含有该元素
for (int i = 0; i < this.usedSize; i++) {
if (this.elements[i] == toFind) {
return true;
}
}
return false;
}
如果里面的每一个元素都是引用类型,需要使用equals()来进行比较,如果是自定义类型,还需要自己覆盖equals()方法。
public boolean contains(int toFind) {
//1.判断数组是否为空
isEmpty();
//2.判断数组中是否含有该元素
for (int i = 0; i < this.usedSize; i++) {
if (this.elements[i].equals(toFind)) {
return true;
}
}
return false;
}
【讲解部分三】:
public void remove(int toRemove) {
//获取要删除的元素对应的下标
int index = indexOf(toRemove);
//判断下标
if (index != -1) {
//将index之后的所有元素向前移动一位
for (int i = index; i < this.usedSize - 1; i++) {
this.elements[i] = this.elements[i + 1];
}
//删除元素之后,数组计数器减一
this.usedSize--;
}
}
如果里面的每一个元素都是引用类型,需要对最后一个元素设置为null,如果不这样做,那么你对最后一个对象的引用将遗留而造成内存泄漏。
【讲解部分四】:
public void clear() {
this.usedSize = 0;
}
如果数组中每一个元素都是引用类型,将this.usedSize = 0;时,会发生内存泄露,原因是JVM只有当对象没有被引用时,才会进行对象回收,this.usedSize = 0;时并没将元素置为null,顺序表仍然引用对象,JVM无法进行回收。
方式一:
public void clear() {
this.element= null;
this.usedSize = 0;
}
较为暴力,不推荐
方式二:
public void clear() {
for(int i = 0;i < this.usedSize;i++){
this.element[i] = null;
}
this.usedSize = 0;
}
比较建议,使用垃圾回收器(GC)进行回收