线性表概念
线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。线性表是最基本、最简单、也是最常用的一种数据结构,常见的线性表:顺序表、链表、栈、队列…
本章学习的是属于线性表中的一种:顺序表
顺序表
顺序表是用一段物理 (内存) 地址连续的存储单元依次存储数据元素的线
性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储。
- 动态顺序表:使用动态开辟的数组存储。
静态顺序表适用于确定知道需要存多少数据的场景.静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用.相比之下动态顺序表更灵活, 根据需要动态的分配空间大小,本章主要介绍动态顺序表,相信看完了动态顺序表,实现静态顺序表则轻而易举。
顺序表主体
顺序表其实底层就是数组实现的,主体部分需要定义一个数组和一个计数器来存 储当前数组中有多少个数据,在构造方法中根据情 况初始化数组大小。
方法实现
接下来我们的任务就是实现以下方法,为了代码可读性,后续实现顺序可能会图片顺序不一致,但总体来说都是一样的。
获取顺序表长度
顾名思义,就是把顺序表中的有效数据返回给调用者,在定义顺序表的时候我们就定义了一个记录有效数据的计数器usedSize,这里只需要将usedSize返回回去就ok了。
新增元素
思路: 实现在 pos 下标位置新增元素,需要考虑 pos 下标是否合法,如果 pos 位置小于0(数组下标从0开始),或者 pos 下标大于有效数据则会发生断层,也是不可以的。
其次还需要考虑扩容的问题,只有当有效数据小于数组预设长度时才能进行添加元素,如果 usedSize 等于数组的长度则需要进行扩容,接下来就是移动数据,只需要定义一个 i 从 usedSize -1的位置 通过循环往后移动数据,最后往 pos 位置进行插入就ok了,并且 usedSize 自增一次。
查找某个元素对应的位置
数组中有该元素返回对应的下标,无则返回-1(数组下标最小是0)。
思路:循环遍历有效数据,相等返回对应下标,退出循环则代表没找到返回-1
判断是否包含某个元素
包含则返回true,无则返回false
思路:调用刚写的查找元素位置的方法,返回的不是-1则返回true,否则返回false
获取 pos 位置的元素
思路:先考虑 pos 位置是否合法,如果 pos 位置大于 usedSize -1或者小于0的情况下,则会造成越界访问。pos 合法的情况直接返回pos 下标对应的元素
修改元素
思路:先考虑 pos 位置是否合法,如果 pos 位置大于 usedSize -1或者小于0的情况下,则会造成越界访问。pos 合法的情况把pos 下标对应的元素更改成value
删除元素
思路:调用刚写的 search 方法,定义 pos 接受返回的下标,如果不等于-1则使用循环从 pos 位置至 usedSize -1的位置开始往后覆盖元素,最后 usedSize自减一次。
打印顺序表
思路:循环遍历有效数据进行输出
清空顺序表
只需将 usedSize 置0就ok了
以上就是实现顺序表的过程啦,希望对读者能带来帮助,最后附上源代码
public class MyArrayList {
public int[] elem;
public int usedSize;
public MyArrayList() {
this.elem = new int[5];//初始化数组大小
}
// 在 pos 位置新增元素
public void add(int pos, int data) {
//先判断pos合法性
if (pos < 0 || pos > this.usedSize) {
System.out.println("pos:" + pos + " 位置不合法!");
return;
}
//扩容
if (this.usedSize == this.elem.length) {
this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
}
//插入找到最后一个有效的数据,然后把 i的元素移到i+1的地方去
int i = 0;
for (i = usedSize - 1; i >= pos; i--) {
this.elem[i + 1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
// 打印顺序表
public void display() {
for (int i = 0; i < usedSize; i++) {
System.out.print(elem[i] + " ");
}
System.out.println();
}
// 判定是否包含某个元素
public boolean contains(int toFind) {
if (this.search(toFind) != -1) {
return true;
}
return false;
}
// 查找某个元素对应的位置
public int search(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return i;
}
}
return -1;
}
// 获取 pos 位置的元素
public int getPos(int pos) {
if (pos < 0 || pos > this.usedSize - 1) {
System.out.println("pos:" + pos + " 位置不合法");
return -1;
}
return this.elem[pos];
}
// 给 pos 位置的元素设为 value
public void setPos(int pos, int value) {
if (pos < 0 || pos > this.usedSize - 1) {
System.out.println("pos:" + pos + " 位置不合法");
return;
}
this.elem[pos] = value;
}
//删除第一次出现的关键字key
public void remove(int toRemove) {
//1、找到要删除的关键字的位置
int pos = this.search(toRemove);
if (pos == -1) {
System.out.println("没有该关键字" + toRemove);
return;
}
for (int i = pos; i < this.usedSize - 1; i++) {
this.elem[i] = this.elem[i + 1];
}
this.usedSize--;
}
// 获取顺序表长度
public int size() {
return usedSize;
}
// 清空顺序表
public void clear() {
this.usedSize = 0;
}
}
总结
顺序表的问题
- 顺序表中间/头部的插入删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
后续我会出一篇关于链表的博客,读者可以自行对比两种数据结构的区别,感兴趣的读者话可以一键三连,防止迷路。