顺序表
顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系。
顺序表API设计
类名 | SequenceList |
---|---|
构造方法 | SequenceList(int capacity):创建容量为capacity的SequenceList对象 |
成员方法 | 1.public void clear():空置线性表 |
2.publicboolean isEmpty():判断线性表是否为空,是返回true,否返回false | |
3.public int length():获取线性表中元素的个数 | |
4.public T get(int i):读取并返回线性表中的第i个元素的值 | |
5.public void insert(int i,T t):在线性表的第i个元素之前插入一个值为t的数据元素。 | |
6.public void insert(T t):向线性表中添加一个元素t | |
7.public T remove(int i):删除并返回线性表中第i个数据元素。 | |
8.public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返 | |
回-1 | |
成员变量 | 1.private T[] eles:存储元素的数组 |
2.private int N:当前线性表的长度 |
顺序表的代码实现
// 顺序表
public class SequenceList<T> implements Iterable<T> {
//存储元素的数组
private T[] eles;
//记录当前顺序表中的元素个数
private int N;
//构造方法
public SequenceList(int capacity) {
//初始化数组
this.eles = (T[]) new Object[capacity];
//初始化长度
this.N = 0;
}
//将一个线性表置为空表
public void clear() {
this.N = 0;
}
//判断当前线性表是否为空表
public boolean isEmpty() {
return N == 0;
}
//获取线性表的长度
public int length() {
return N;
}
//获取指定位置的元素
public T get(int i) {
return eles[i];
}
//向线型表中添加元素t
public void insert(T t) {
if (N == eles.length) {
resize(2 * eles.length);
}
eles[N++] = t;
}
//在i元素处插入元素t
public void insert(int i, T t) {
if (N == eles.length) {
resize(2 * eles.length);
}
//先把i索引处的元素及其后面的元素依次向后移动一位
for (int index = N; index > i; index--) {
eles[index] = eles[index - 1];
}
//再把t元素放到i索引处即可
eles[i] = t;
//元素个数+1
N++;
}
//删除指定位置i处的元素,并返回该元素
public T remove(int i) {
//记录索引i处的值
T current = eles[i];
//索引i后面元素依次向前移动一位即可
for (int index = i; index < N - 1; index++) {
eles[index] = eles[index + 1];
}
//元素个数-1
N--;
if (N < eles.length / 4) {
resize(eles.length / 2);
}
return current;
}
//查找t元素第一次出现的位置
public int indexOf(T t) {
for (int i = 0; i < N; i++) {
if (eles[i].equals(t)) {
return i;
}
}
return -1;
}
//根据参数newSize,重置eles的大小
public void resize(int newSize) {
//定义一个临时数组,指向原数组
T[] temp = eles;
//创建新数组
eles = (T[]) new Object[newSize];
//把原数组的数据拷贝到新数组即可
for (int i = 0; i < N; i++) {
eles[i] = temp[i];
}
}
@Override
public Iterator<T> iterator() {
return new SIterator();
}
private class SIterator implements Iterator {
private int cusor;
public SIterator() {
this.cusor = 0;
}
@Override
public boolean hasNext() {
return cusor < N;
}
@Override
public Object next() {
return eles[cusor++];
}
}
}
// 测试代码
public class SequenceListTest {
public static void main(String[] args) {
//创建顺序表对象
SequenceList<String> sl = new SequenceList<>(10);
//测试插入
sl.insert("姚明");
sl.insert("科比");
sl.insert("麦迪");
sl.insert(1, "詹姆斯");
for (String s : sl) {
System.out.println(s);
}
System.out.println("------------------------------------------");
//测试获取
String getResult = sl.get(1);
System.out.println("获取索引1处的结果为:" + getResult);
//测试删除
String removeResult = sl.remove(0);
System.out.println("删除的元素是:" + removeResult);
//测试清空
sl.clear();
System.out.println("清空后的线性表中的元素个数为:" + sl.length());
}
}
public class SequenceListTest2 {
public static void main(String[] args) {
SequenceList<String> sl = new SequenceList<>(3);
sl.insert("张三");
sl.insert("李四");
sl.insert("王五");
sl.insert("赵六");
}
}
顺序表的遍历
-
一般作为容器存储数据,都需要向外部提供遍历的方式,因此我们需要给顺序表提供遍历方式。
-
在java中,遍历集合的方式一般都是用的是foreach循环,如果想让我们的SequenceList也能支持foreach循环,则需要做如下操作:
-
1.让SequenceList实现Iterable接口,重写iterator方法;
-
2.在SequenceList内部提供一个内部类SIterator,实现Iterator接口,重写hasNext方法和next方法;
-
顺序表的容量可变
-
在之前的实现中,当我们使用SequenceList时,先new SequenceList(5)创建一个对象,创建对象时就需要指定容器的大小,初始化指定大小的数组来存储元素,当我们插入元素时,如果已经插入了5个元素,还要继续插入数据,则会报错,就不能插入了。这种设计不符合容器的设计理念,因此我们在设计顺序表时,应该考虑它的容量的伸缩性。
-
考虑容器的容量伸缩性,其实就是改变存储数据元素的数组的大小,那我们需要考虑什么时候需要改变数组的大小?
-
1.添加元素时(扩容):
-
添加元素时,应该检查当前数组的大小是否能容纳新的元素,如果不能容纳,则需要创建新的容量更大的数组,我们这里创建一个是原数组两倍容量的新数组存储元素。
-
-
2.移除元素时(缩容):
-
移除元素时,应该检查当前数组的大小是否太大,比如正在用100个容量的数组存储10个元素,这样就会造成内存空间的浪费,应该创建一个容量更小的数组存储元素。如果我们发现数据元素的数量不足数组容量的1/4,则创建一个是原数组容量的1/2的新数组存储元素。
-
顺序表的时间复杂度
-
get(i):不难看出,不论数据元素量N有多大,只需要一次eles[i]就可以获取到对应的元素,所以时间复杂度为O(1);
-
insert(int i,T t):每一次插入,都需要把i位置后面的元素移动一次,随着元素数量N的增大,移动的元素也越多,时间复杂为O(n);
-
remove(int i):每一次删除,都需要把i位置后面的元素移动一次,随着数据量N的增大,移动的元素也越多,时间复杂度为O(n);
-
由于顺序表的底层由数组实现,数组的长度是固定的,所以在操作的过程中涉及到了容器扩容操作。这样会导致顺序表在使用过程中的时间复杂度不是线性的,在某些需要扩容的结点处,耗时会突增,尤其是元素越多,这个问题越明显
java中ArrayList实现
- java中ArrayList集合的底层也是一种顺序表,使用数组实现,同样提供了增删改查以及扩容等功能。
- 1.是否用数组实现;
- 2.有没有扩容操作;
- 3.有没有提供遍历方式;
- ArrayList通用性强,有时候我们需要自己设计用于处理特殊情况的顺序表