概述
一个线性表是n个具有相同特性的数据元素的有限序列。
前驱元素:若A元素在B元素的前面,则称A为B的前驱元素
后继元素:若B元素在A元素的后面,则称B为A的后继元素
特征
头结点:没有前驱元素。
尾结点:没有后继元素。
分类
顺序表:存储的地址连续。最常见的数组
链表:存储的地址顺序并不连续。
示例代码
项目结构:
新建SequenceList.java类:
import java.util.Iterator;
/**
* @version:
* @author: 零乘一
* @description: 类的简介
* @date: 2021/10/8 15:45
**/
public class SequenceList<T> implements Iterable<T>{
private T[] eles;//存储元素的数组
private int N;//当前线性表的长度
public SequenceList(int capacity) {
eles = (T[]) new Object[capacity];
N = 0;
}
//空置线性表
public void clear(){
N = 0;
}
//判断线性表是否为空,是返回true,否返回false
public boolean isEmpty(){
return N==0;
}
//获取线性表中元素的个数
public int length(){
return N;
}
//读取并返回线性表中的第i个元素的值
public T get(int i){
if (i < 0 || i > N){
throw new RuntimeException("当前元素不存在!");
}
return eles[i];
}
//在线性表的第i个元素之前插入一个值为t的数据元素
public void insert(int i, T t){
//判断插入的位置是否正确
if (i<0 || i>N){
throw new RuntimeException("插入位置不合法");
}
//插入的元素是否合法
if (t == null){
throw new RuntimeException("插入元素不合法");
}
//若元素已满,则需要进行扩容
if (N==eles.length){
resize(eles.length*2);
}
//i位置的后的元素全部后移一个位置
for (int j = N; j > i; j--) {
eles[j]=eles[j-1];
}
//在j的位置插入该元素
eles[i] = t;
//元素个数+1
N++;
}
//向线性表中添加一个元素t
public void insert(T t){
//判断插入的元素是否合法
if (t == null){
throw new RuntimeException("插入的元素不合法");
}
if (N == eles.length){
resize(eles.length*2);
}
eles[N++] = t;
}
//删除并返回线性表中的第i个数据元素
public T remove(int i){
if (i<0 || i>N-1){
throw new RuntimeException("当前要删除的元素不存在");
}
//记录i位置处的元素
T t = eles[i];
//i位置后面的所有元素都向前移动一个位置
for (int j = i; j < N - 1; j++) {
eles[j] = eles[j+1];
}
//当前元素数量-1
N--;
//当元素数量不足数组大小的1/4,则重置数组的大小
if (N>0 && N<eles.length/4){
resize(eles.length/2);
}
return t;
}
//返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1
public int indexOf(T t){
if (t == null){
throw new RuntimeException("查找的元素不合法");
}
for (int i = 0; i < N; i++) {
if (eles[i].equals(t)){
return i;
}
}
return -1;
}
//打印当前线性表的元素
public void showEles(){
for (int i = 0; i < N; i++) {
System.out.print(eles[i]+" ");
}
System.out.println();
}
@Override
public Iterator iterator() {
return new SIterator();
}
//自定义的内部类实现Iterator接口作为方法iterator的返回参数
private class SIterator implements Iterator{
private int cur;
public SIterator(){
this.cur = 0;
}
@Override
public boolean hasNext() {
return cur<N;
}
@Override
public T next() {
return eles[cur++];
}
}
//改变容量
public void resize(int newSize){
//记录旧数组
T[] temp = eles;
//创建新数组
eles = (T[]) new Object[newSize];
//把旧数组中的元素拷贝到新数组中
for (int i = 0; i < N; i++) {
eles[i] = temp[i];
}
}
public int capacity(){
return eles.length;
}
}
新建SequenceListTest .java:
/**
* @version:
* @author: 零乘一
* @description: 类的简介
* @date: 2021/10/8 18:14
**/
class SequenceListTest {
public static void main(String[] args) throws Exception{
SequenceList<String> sequence = new SequenceList<>(5);
//测试遍历
sequence.insert(0,"姚明");
sequence.insert(1,"科比");
sequence.insert(2,"麦迪");
sequence.insert(3,"艾弗森");
sequence.insert(4,"卡特");
System.out.println(sequence.capacity());
sequence.insert(0,"123");//需要进行扩容操作后才能插入该元素
System.out.println(sequence.capacity());//因为每一次扩容后都会以数组的两倍进行扩容
sequence.insert("2");
sequence.insert("2");
sequence.insert("2");
sequence.insert("2");
System.out.println(sequence.capacity());
sequence.insert("2");
System.out.println(sequence.length());
System.out.println(sequence.capacity());
sequence.remove(0);
sequence.remove(0);
sequence.remove(0);
sequence.remove(0);
sequence.remove(0);
sequence.remove(0);
sequence.remove(0);
//当数组中的元素小于数组空间大小的1/4时,就缩小一半的空间
System.out.println(sequence.capacity());
}
}
代码解析
这个部分是为这个类提供foreach循环的条件。
必须要写这些部分才能进行foreach循环。
否则就会报出这样的错误:
foreach not applicable to type ‘SequenceList<java.lang.String>’
时间复杂度
获取元素的get()复杂度:O(1)
指定位置插入元素insert(int i,T t):主要看元素插入的位置,随着数据量N的增大,移动的元素也越来越多,时间复杂度时O(n)(理解:当插入的位置是在0时,所有的元素都必须向后移动一次,)
删除元素remove(int i):每删除一个元素,所有的元素都必须向前移动一次,时间复杂度是O(n)(理解:当删除的位置是在0时,所有的元素都必须向前移动一次)
由于顺序表的底层由数组实现,数组的长度是固定的,所以在操作的过程中涉及到了容器扩容操作。这样会导致顺
序表在使用过程中的时间复杂度不是线性的,在某些需要扩容的结点处,耗时会突增,尤其是元素越多,这个问题
越明显。——黑马程序员
因为在扩容的操作中,是新建一个新的数组,然后将旧数组中的数据重新写入新数组中,在插入元素且恰好需要扩容时,当元素越来越多,时间复杂度会越来越大。
顺序表使用实例
java中ArrayList集合的底层也是一种顺序表,使用数组实现,同样提供了增删改查以及扩容等功能。
1.是否用数组实现。
2.有没有扩容操作。
3.有没有提供遍历方式。
ArrayList代码分析
elementData是使用数组实现的。
确定数组的内部的长度:Alt+点击红色方框圈起来的方法追踪找到数组扩容的方法。
在ArrayList类中也定义了一个内部类并且实现了Iterator接口
依照顺序表的特性:
1.是否用数组实现。
2.有没有扩容操作。
3.有没有提供遍历方式。
而ArrayList中的源码部分都有满足这个部分的代码,说明ArrayList是使用顺序表进行实现的。
总结
查询快,增删慢。因为在顺序表中,所有的元素地址都是连着的,没有空余的位置,因此每一次增删操作都会伴随着大量元素位置的移动。