线性表简述
线性表是最基本、最简单、也是最常用的一种数据结构。一个线性表是n个具有相同特性的数据元素的有限序列。
- 前驱元素(Previous):若A元素在B元素的前面,则称A为B的前驱元素
- 后继元素(Next):若B元素在A元素的后面,则称B为A的后继元素
**线性表的特征:**数据元素之间具有一种“一对一”的逻辑关系。
- 第一个数据元素没有前驱,这个数据元素被称为头结点(head);
- 最后一个数据元素没有后继,这个数据元素被称为尾结点(last);
- 除了第一个和最后一个数据元素外,其他数据元素有且仅有一个前驱(Previous)和一个后继(Next)。
如果把线性表用数学语言来定义,则可以表示为(a1,…ai-1,ai,ai+1,…an),ai-1领先于ai,ai领先于ai+1,称ai-1是ai的前驱元素,ai+1是ai的后继元素
线性表的分类:
线性表中数据存储的方式可以是顺序存储,也可以是链式存储,按照数据的存储方式不同,可以把线性表分为顺序表和链表。
1.顺序表
顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系。
1.1 顺序表的实现
顺序表API设计:
1.2 顺序表的遍历
一般作为容器存储数据,都需要向外部提供遍历的方式,因此我们需要给顺序表提供遍历方式。
在java中,遍历集合的方式一般都是用的是foreach循环,如果想让我们的SequenceList也能支持foreach循环,则需要做如下操作:
1.让SequenceList实现Iterable接口,重写iterator方法;
2.在SequenceList内部提供一个内部类SIterator,实现Iterator接口,重写hasNext方法和next方法;
1.3 顺序表的容量可变
在之前的实现中,当我们使用SequenceList时,假设new SequenceList(5)创建一个对象,创建对象时就需要指定容器的大小,初始化指定大小的数组来存储元素,当我们插入元素时,如果已经插入了5个元素,还要继续插入数据,则会报错,就不能插入了。这种设计不符合容器的设计理念,因此我们在设计顺序表时,应该考虑它的容量的伸缩性。
1.添加元素时:
添加元素时,应该检查当前数组的大小是否能容纳新的元素,如果不能容纳,则需要创建新的容量更大的数组,我们这里创建一个是原数组两倍容量的新数组存储元素。
2.移除元素时:
移除元素时,应该检查当前数组的大小是否太大,比如正在用100个容量的数组存储10个元素,这样就会造成内存空间的浪费,应该创建一个容量更小的数组存储元素。如果我们发现数据元素的数量不足数组容量的1/4,则创建一个是原数组容量的1/2的新数组存储元素。
1.4 顺序表的时间复杂度
- get(i):不难看出,不论数据元素量N有多大,只需要一次eles[i]就可以获取到对应的元素,所以时间复杂度为O(1);
- insert(int i,T t):每一次插入,都需要把i位置后面的元素移动一次,随着元素数量N的增大,移动的元素也越多,时间复杂为O(n);
- remove(int i):每一次删除,都需要把i位置后面的元素移动一次,随着数据量N的增大,移动的元素也越多,时间复杂度为O(n);
由于顺序表的底层由数组实现,数组的长度是固定的,所以在操作的过程中涉及到了容器扩容操作。这样会导致顺序表在使用过程中的时间复杂度不是线性的,在某些需要扩容的结点处,耗时会突增,尤其是元素越多,这个问题越明显。
1.5 java中ArrayList实现(源码的了解)
java中ArrayList集合的底层也是一种顺序表,使用数组实现,同样提供了增删改查以及扩容等功能。
1.有用数组实现;
2.包含扩容操作;
3.有提供遍历方式;
顺序表的代码功能实现
下面展示一些 顺序表的代码功能实现
。
import java.util.Iterator;
/**
* @author shkstart
* @create 2021-08-09 19:15
*/
//遍历集合一边是采用foreach的方式,想要SequenceList也能支持foreach需要实现Iterable接口
public class SequenceList<T> implements Iterable{
private T[] elements;//存储元素的数组
private int Num;//当前线性表的长度
//创建容量为capacity的SequenceList的对象
public SequenceList(int capacity) {
//构建长度为capacity的存储元素的数组,Object可以存储任意形式的数组,满足T[]反省的要求,强转为T[]
elements = (T[]) new Object[capacity];
Num = 0;
}
//设置线性表为空表
public void clear() {
Num = 0;
}
//判断线性表是否为空
public boolean isEmpty() {
return Num == 0;
}
//获取线性表中元素的个数
public int length() {
return Num;
}
//读取并返回线性表中第i元素的值
public T get(int i){
if (Num == 0 && i >= Num) {
throw new IllegalStateException("当前元素不存在");
}
return elements[i];
}
//向线性表中添加一个元素t
public void insert(T t){
if (Num == elements.length){
resize(elements.length * 2);
}
elements[Num++] = t;
}
//在线性表的第i个元素之前插入一个值为t的数据元素
public void insert(int i,T t){
//i = Num相当于在尾部添加元素t
if (i < 0 || i > Num){
throw new IllegalStateException("插入的位置有误");
}
//插入元素,插入的元素成为第i个元素,原有的第i后的元素都向后移一位
// //安全性检验
// if (i == elements.length) {
// throw new IllegalStateException("当前元素已填满");
// }
//数据进行扩容后,就不存在由于集合元素装满而无法再加入的情况
if (Num == elements.length) {
resize(elements.length * 2);
}
for (int index = Num; index > i ; index--) {
elements[index] = elements[index - 1];
}
//把t加入到i位置
elements[i] = t;
//元素个数加一位
Num++;
}
//删除并返回线性表中第i个数据元素
public T remove(int i){
//安全性检验,i = Num(相当于T.length) 时,角标越界
if (i < 0 || i > Num - 1){
throw new IllegalStateException("移除的元素不存在");
}
//移除的元素一定要先声明,通过元素的移位后,索引将会向前一位
T result = elements[i];
//把i位置之后的元素向前移一位
for (int index = i; index < Num - 1; index++) {
elements[index] = elements[index + 1];
}
//元素个数减少一位
Num--;
if (Num > 0 && Num < elements.length / 4){
resize(elements.length / 2);
}
return result;
}
//返回线性表中首次出现的指定的数据元素的位序号,若不寻在,则返回-1
public int indexOf(T t){
//安全性检验
if (Num == 0) {
throw new IllegalStateException("数组中不存在该元素!");
}
if (t == null){
throw new IllegalStateException("该元素类型不合法!");
}
for (int index = 0; index < Num; index++) {
if (elements[index].equals(t)){
return index;
}
}
return -1;
}
//根据扩容需要进行元素长度扩张
public void resize(int newSize){
//记录旧集合元素个数
T[] temp = elements;
//创建新数组
elements = (T[]) new Object[newSize];
//把旧集合元素复制到新集合中
for (int i = 0; i < Num; i++) {
elements[i] = temp[i];
}
}
//将新集合的长度进行更新
public int capacity() {
return elements.length;
}
//打印当前线性表的元素
public void printElements(){
for (int i = 0; i < Num ; i++) {
System.out.print(elements[i] + " ");
}
System.out.println();
}
@Override
public Iterator iterator() {
return new SequenceIterator();
}
private class SequenceIterator implements Iterator {
private int cur;
public SequenceIterator() {
this.cur = 0;
}
@Override
public boolean hasNext() {
return cur < Num;
}
@Override
public T next() {
return elements[cur++];
}
}
}
下面展示一些 顺序表的代码功能测试1
。
/**
* @author shkstart
* @create 2021-08-09 19:17
*/
public class SequenceListTest {
public static void main(String[] args) {
//创建顺序列表的对象
SequenceList<String> sequenceList = new SequenceList<>(10);
//插入测试
sequenceList.insert("姚明");
sequenceList.insert("麦迪");
sequenceList.insert("科比");
sequenceList.insert("库里");
sequenceList.insert("奥尼尔");
//测试获取
String getResult = sequenceList.get(0);
System.out.println("获取的元素是:" + getResult);
System.out.println("--------------");
//测试删除
String removeResult = sequenceList.remove(2);
System.out.println("删除的元素是:" + removeResult);
System.out.println("--------");
for (Object s : sequenceList){
System.out.println(s);
}
//测试清空
sequenceList.clear();
System.out.println("清空后线性表的元素个数:" + sequenceList.length());
}
}
下面展示一些 顺序表的代码功能测试2
。
/**
* @author shkstart
* @create 2021-08-10 10:12
*/
//检验顺序列表是否能够扩容
public class SequenceListTest1 {
public static void main(String[] args) {
//创建顺序列表的对象
SequenceList<String> sequenceList = new SequenceList<>(5);
//插入测试
sequenceList.insert("姚明");
sequenceList.insert("麦迪");
sequenceList.insert("科比");
sequenceList.insert("库里");
sequenceList.insert("奥尼尔");
sequenceList.insert("哈登");
//在之前的方法中,构建capacity方法就是为了检验扩容后,集合的长度是否实现了正常变换
System.out.println(sequenceList.capacity());
//测试获取
String getResult = sequenceList.get(0);
System.out.println("获取的元素是:" + getResult);
System.out.println("--------------");
//测试删除
String removeResult = sequenceList.remove(0);
String removeResult1 = sequenceList.remove(0);
String removeResult2 = sequenceList.remove(0);
String removeResult3 = sequenceList.remove(0);
String removeResult4 = sequenceList.remove(0);
System.out.println("删除的元素是:" + removeResult + "," +
removeResult1 + "," + removeResult2 + "," + removeResult3 + "," + removeResult4);
System.out.println(sequenceList.capacity());
System.out.println("--------");
for (Object s : sequenceList){
System.out.println(s);
}
//测试清空
sequenceList.clear();
System.out.println("清空后线性表的元素个数:" + sequenceList.length());
}
}