顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。顺序表一般可以分为: 静态顺序表:使用定长数组存储。 动态顺序表:使用动态开辟的数组存储。 静态顺序表适用于确定知道需要存多少数据的场景。 静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用. 相比之下动态顺序表更灵活, 根据需要动态的分配空间大小。
下面,我们来实现一个动态的顺序表。
一,要实现的功能
public class SeqList {
// 打印顺序表
public void display() { }
// 在 pos 位置新增元素
public void add(int pos, int data) { }
// 判定是否包含某个元素
public boolean contains(int toFind) { return true; }
// 查找某个元素对应的位置
public int search(int toFind) { return -1; }
// 获取 pos 位置的元素
public int getPos(int pos) { return -1; }
// 给 pos 位置的元素设为 value
public void setPos(int pos, int value) { }
//删除第一次出现的关键字key
public void remove(int toRemove) { }
// 获取顺序表长度
public int size() { return 0; }
// 清空顺序表
public void clear() { }
}
二,接口的实现
1,构建表的框架
由上面的图片可以知道, 我们需要创建一个数组(int[] elem),由自己往里面添加元素。同时还需要知道这个表中的数组开辟的大小,这个数组开辟的大小由自己决定。在这里,我们假设初始开辟的容量(int IntCapacity)大小为10。虽然我们开辟了这么大的表,但是不一定每一个数组里面都有元素。这个时候,我们需要知道它的真实的大小,也就是有效的大小(int usedSize)。
public int[] elem;
//有效的数据个数
public int usedSize;
//初始容量,且不能被改变
public static final int intCapacity = 10;
//创建对象并调用合适的构造方法
public MyArrayList() {
//创建一个初始容量为10个大小的数组对象
this.elem = new int[intCapacity];
this.usedSize = 0;
}
2,打印顺序表
首先要对顺序表进行打印。只有打印之后,我们才能知道后续的功能的实现是否正确。刚开始的时候,表里的内容是空的,这样,打印出来的自然是空表。这里,我们假设表是有内容的。我们只需要对这个表进行遍历,就可以了。要注意的是打印这个表里面有效的元素,需要明确遍历的终止条件,是IntCapacity还是usedSize。usedSize是这个顺序表的有效数据的大小。如果使用IntCapacity,当这个表里面的数据不满的时候,在有效数据大小范围之外打印的会是0。
// 打印顺序表
public void display() {
int i = 0;
for(i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i] + " ");
}
System.out.println();
//System.out.print(Arrays.toString(this.elem));
}
3,在 pos 位置新增元素
在这里,我们需要考虑的:1,如何实现?2,输入的pos位置在这个数组的有效范围内吗?3,这个数组会不会满?满了该什么办?
我们首先先考虑如何实现这个问题。先找到pos位置之后,要移动这个里面的元素,给要插入的元素留下位置,否者直接插入会覆盖pos位置的内容。所以,要对pos后面的元素进行移动。移动的时候,要从最后一个开始移动,否则,从pos位置移动,还是会造成被覆盖的问题。完成之后,让usedSize++就可以了。
接下来,考虑问题2和问题3。对于问题2,pos在有效的范围之内,能够进行操作。这个范围就是从0号下标开始到usedSizes - 1下标。一旦出了这个范围,就是不合法的,需要进行警告。你可以使用异常,也可以进行打印。问题3,我们需要判断满的情况。在添加元素的时候,usedSize(有效长度)在累加,当元素加到足够多的时候,usedSize就会和数组的容量长度相等。这里,就是判断是否需要扩容的条件。同时要注意,初始容量是不能改变的,所以在扩大容量的时候,需要拷贝数组来进行扩容。使用Arrays中的 copyOf方法就可以实现对数组的拷贝,同时扩大容量。
//检查pos位置合法性
private void checkPos(int pos) {
if(pos < 0 || pos > this.usedSize) {
throw new RuntimeException("pos位置不合法!");
}
}
//检查数组是否满的情况
private boolean isFull() {
if(this.elem.length == this.usedSize) {
return true;
}
return false;
}
// 在 pos 位置新增元素
public void add(int pos, int data) {
//pos位置合法性
checkPos(pos);
//检查数组是否满了
if(isFull()) {
this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
}
//新增元素
for(int i = this.usedSize - 1; i >= pos; i--) {
this.elem[i + 1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
4,判定是否包含某个元素
这个只需要进行遍历,然后判断就可以了。
// 判定是否包含某个元素
public boolean contains(int toFind) {
for(int i = 0; i < this.usedSize; i++) {
if(this.elem[i] == toFind) {
return true;
}
}
return false;
}
5,查找某个元素对应的位置
这个只需要进行遍历,然后判断就可以了。合法性检查的函数在前面已经写过了,直接调用就可以了。
// 查找某个元素对应的位置
public int search(int toFind) {
//合法性检查
checkPos(toFind);
for(int i = 0; i < this.usedSize; i++) {
if(this.elem[i] == toFind) {
return i;
}
}
return -1;
}
6,获取 pos 位置的元素
需要考虑的问题:1,如何查找?2,合法性?3,表是否为空?
前面我们写过了pos合法性的判断的函数,这里只需要进行调用就可以了。现在我们来解决问题3。为什么要判断表是否为空?表为空,说明它的有效长度为0,也就是里没有元素可以返回,不需要进行获取。
//判断是否为空
private boolean isEmpty() {
return this.usedSize == 0;
}
// 获取 pos 位置的元素
public int getPos(int pos) {
//表是否为空
if (isEmpty()) {
//也可以进行打印
throw new RuntimeException("表为空!");
}
//检查合法性
if(pos < 0 || pos >= this.usedSize) {
throw new RuntimeException("pos位置不合法!");
}
//获取元素
return this.elem[pos];
}
7,给 pos 位置的元素设为 value
首先进行合法性的检查,然后覆盖掉值就可以了。表是否为空要进行判断。
// 给 pos 位置的元素设为 value
public void setPos(int pos, int value) {
checkPos(pos);
if(isFull()) {
throw new RuntimeException("表为空!");
}
this.elem[pos] = value;
}
8,删除第一次出现的关键字key
删除之后,让有效长度--。
//删除第一次出现的关键字key
public void remove(int toRemove) {
int pos = search(toRemove);
if(pos == -1) {
System.out.println("未找到该值!");
return;
}
for(int i = pos; i < this.usedSize - 1; i++) {
this.elem[i] = this.elem[i + 1];
}
this.usedSize--;
}
9,获取顺序表长度
顺序表的长度就是它的有效长度。
// 获取顺序表长度
public int size() {
if (!isEmpty()) {
return this.usedSize;
}
return 0;
}
10,清除顺序表
让有效长度为0。
// 清空顺序表
public void clear() {
this.usedSize = 0;
}
三,全部代码及结果
import java.util.Arrays;
/**
* @author: Naion
* @create: 2021-12-26 14:56
**/
public class MySeqlist {
//定义一个顺序表
private int[] elem;
private int usedSize;
private int intCapacity = 10;
//调用合适的构造方法
public MySeqlist() {
//定义大小
this.elem = new int[intCapacity];
this.usedSize = 0;
}
// 打印顺序表
public void display() {
int i = 0;
for(i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i] + " ");
}
System.out.println();
}
//检查pos位置合法性
private void checkPos(int pos) {
if(pos < 0 || pos > this.usedSize) {
throw new RuntimeException("pos位置不合法!");
}
}
//检查数组是否满的情况
private boolean isFull() {
if(this.elem.length == this.usedSize) {
return true;
}
return false;
}
// 在 pos 位置新增元素
public void add(int pos, int data) {
//pos位置合法性
checkPos(pos);
//检查数组是否满了
if(isFull()) {
this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
}
//新增元素
for(int i = this.usedSize - 1; i >= pos; i--) {
this.elem[i + 1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
// 判定是否包含某个元素
public boolean contains(int toFind) {
for(int i = 0; i < this.usedSize; i++) {
if(this.elem[i] == toFind) {
return true;
}
}
return false;
}
// 查找某个元素对应的位置
public int search(int toFind) {
//合法性检查
checkPos(toFind);
for(int i = 0; i < this.usedSize; i++) {
if(this.elem[i] == toFind) {
return i;
}
}
return -1;
}
//判断是否为空
private boolean isEmpty() {
return this.usedSize == 0;
}
// 获取 pos 位置的元素
public int getPos(int pos) {
//表是否为空
if (isEmpty()) {
throw new RuntimeException("表为空!");
}
//检查合法性
if(pos < 0 || pos >= this.usedSize) {
throw new RuntimeException("pos位置不合法!");
}
//获取元素
return this.elem[pos];
}
// 给 pos 位置的元素设为 value
public void setPos(int pos, int value) {
checkPos(pos);
if(isFull()) {
throw new RuntimeException("表为空!");
}
this.elem[pos] = value;
}
//删除第一次出现的关键字key
public void remove(int toRemove) {
int pos = search(toRemove);
if(pos == -1) {
System.out.println("未找到该值!");
return;
}
for(int i = pos; i < this.usedSize - 1; i++) {
this.elem[i] = this.elem[i + 1];
}
this.usedSize--;
}
// 获取顺序表长度
public int size() {
if (!isEmpty()) {
return this.usedSize;
}
return 0;
}
// 清空顺序表
public void clear() {
this.usedSize = 0;
}
}
/**
* @author: Naion
* @create: 2021-12-26 14:55
**/
public class test_12_26_01 {
public static void main(String[] args) {
MySeqlist myseqlist = new MySeqlist();
for(int i = 0; i < 10; i++) {
myseqlist.add(i, i);
}
myseqlist.display();
System.out.println("======");
myseqlist.add(1, 3);
myseqlist.display();
System.out.println("======");
System.out.println(myseqlist.contains(3));
System.out.println("======");
System.out.println(myseqlist.search(3));
System.out.println("======");
System.out.println(myseqlist.getPos(1));
System.out.println("======");
myseqlist.setPos(1, 0);
myseqlist.display();
System.out.println("======");
myseqlist.remove(0);
myseqlist.display();
System.out.println("======");
System.out.println(myseqlist.size());
System.out.println("======");
myseqlist.clear();
myseqlist.display();
}
}
四,顺序表的问题
1. 顺序表中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继 续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
这些问题需要单链表来解决。