1.线性表的定义
线性表,从名字上你就能感觉到,是具有像线 样的性质的表。
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储
这里需要强调几个关键的地方:
首先它是一个
序列
也就是说,元素之间是有顺序的,若元素存在多个,则第 一个 元素无前驱,最后一个元素无后继,其他每个元素都有且只有 一个 前驱和后继
然后,线性表强调是有限的,在计算机中处理的对象都是有限的,那种无限的数列,只存在于数学的概念中。
如果用数学语言来进行定义可如下:
若将线性表记为 (a1, …, ai-1 , ai, ai+1, …, an),则表中 ai-1 领先于先于 ai;ai 领先于 ai+1:称 ai-1是 ai 的直接前驱元素,ai+1 是 ai 的直接后继元素。当 i=1,2,…,n-1 时,ai 有且仅有一个直接后继,当 i=2,3,…,n时,ai有且仅有一个直接前驱。如图所示:
所以线性表元素的个数 n(n≥0) 定义为线性表的长度,当 n=0 时,称为空表。
在非空表中的每个数据元素都有一个确定的位置,如 ai 是第 一个数据元素, an最后一个数据元素,ai 是第 i个 数据元素,称 i 为数据元素 ai 在线性表中的位序。
2.线性表的抽象数据类型
我们已经知道了线性表的定义,现在我们来分析 线性表应该有些什么样的操作呢?
模拟的是String类型数据的顺序表【也可以使用泛型,可以支持更多的数据】
public class myArrayList {
private int[] array;
private int size;
private static int capacity = 5;
public myArrayList() {
this.array = new String[capacity];
this.size = capacity;
}
private void addLast(String e){
}
private void addIndex(int index, String e){
}
private String remove(int index){
return null;}
private boolean remove(String e){
return false;}
private String get(int index){
return null;}
private String set(int index, String e){
return null;}
private boolean contains(String e){
return false;}
private int indexOf(String e){
return -1;}
private int lastIndexOf(String e){
return -1;}
private void clear(){
}
private int size(){
return this.size;}
private boolean isEmpty(){
return this.size == 0;}
}
3.线性表的顺序存储结构
3.1 顺序存储定义
线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素,示意图如下:
3.2 顺序存储方式
private String[] array;
private int size;
private static int capacity = 5;
线性表的顺序存储结构就是在内存中找了块地儿,把一定内存空间结占了,然后把 相同数据类型的数据元素
依次存放在这块空地中。。既然线性表的每个数据元素的类型都相同,所以可以用 Java 语言(其 也相同)的一维数组来实现顺序存储结构, 即把第 1 个数据元素存到数组下标为 0 的位置中,接着把线性表相邻的元素存储在数组中相邻的位置。
这里,我们就发现描述顺序存 结构需要 三个
属性:
- 存储空间的起始位置:数组 array ,它的存储位置就是存储空间的存储位置。
- 线性表的最大存储容量:数组长度capacity
- 线性表的当前长度:size
3.3数据长度和线性表长度的区别
注意哦,这里有两个概念"数组的长度"和"续性表的长度"需要区分一下。
数组的长度是存放线性表的存储空间的长度,存储分配后这个量是一般是不变的。有个别同学可能会问 “ 数组的大小一定不可以变吗?我怎么看到有书中谈到可以动态分配的维数组 “ 。是的, 般高级语言 比如 C++都可以用编程 段实现动态分配数组,不过这会带来性能上的损耗。
线性表的长度是线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是变化的。
在任意时刻,线性表的长度应该小于等于数组的长度。
3.4 地址计算方法
由于我们数数都是从 1 开始数的,线性表的定义也不能免俗,起始也是 1,可是大部分语言中的数组却是从 0 开始第 1个下标的,于是线性表的第 i个 元素是要存储在数组下标为 i-1 的位置,即数据元素的序号和存放它的数组下标之间存在对应关系
假设占用的是 c个 存储单元,那么线性表 i+l 个数据元素的存储位置和第 i个数据元素的存储位置满足下列关系 (LOC 表示获得存储位置的函数)
LOC(ai+1) = LOC(ai)+c
所以对于第 i个数据元素 ai 的存储位置可以由 a1 推算得出:
LOC(ai) = LOC(a1)+(i-1)*c
通过这个公式,你可以随时算出线性表中任意位置的地址,不管它是第 一个 还是最后一个,都是相同的时间。那么我们对每个线性表位置的存入或者取出数据,对于计算机来说都是相等的时间,也就是一个常数,因此用我们算法中学到的时间复杂度的概念来说,它的存取时间性能为 0(1) 。我们通常把具有这一特点的存储结构称为随机存取结构。
4.顺序表的CRUD
4.1 add【增加元素】
// 尾插
private void addLast(String e) {
if (this.size >= capacity) {
realloc();
}
this.array[this.size++] = e;
}
// 任意位置添加
private void addIndex(int index, String e) throws mArrayListOutOfIndexRange {
if (index <= 0 || index - 1 > this.size) {
throw new mArrayListOutOfIndexRange(index + "下标越界");
} else {
if (this.size >= capacity) {
realloc();
}
for (int i = this.size - 1; i >= index; i--) {
this.array[i + 1] = this.array[i];
}
this.array[index - 1] = e;
++this.size;
}
}
@Override
public String toString() {
StringBuffer stringBuffer = new StringBuffer("[");
for (int i = 0; i < this.size; i++) {
stringBuffer.append(this.array[i]);
if (i < this.size - 1) {
stringBuffer.append(", ");
}
}
stringBuffer.append("]");
String result = stringBuffer.toString();
return result;
}
private static void testAddLast() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
System.out.println("testAddLast(): " + arrayList);
}
private static void testAddIndex() throws mArrayListOutOfIndexRange {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addIndex(i, String.valueOf(i));
}
System.out.println("testAddIndex(): " + arrayList);
}
4.2 remove【删除元素】
// 按下标删除
private String remove(int index) throws mArrayListOutOfIndexRange {
if (index <= 0 || index - 1 > this.size) {
throw new mArrayListOutOfIndexRange(index + "越界");
} else {
/*
index-1 是为了消除下标起始 0 开始
this.size-1 是因为一处一个元素后数组长度要减一
*/
String result = this.array[index - 1];
for (int i = index - 1; i < this.size - 1; i++) {
this.array[i] = this.array[i + 1];
}
--this.size;
return result;
}
}
// 按值删除【删除第一次出现的元素】
private boolean remove(String e) {
for (int i = 0; i < this.size - 1; i++) {
if (this.array[i].equals(e)) {
for (int j = i; j < this.size - 1; j++) {
this.array[j] = this.array[j + 1];
}
--this.size;
return true;
}
}
return false;
}
public static void testRemoveIndexAndValue() throws mArrayListOutOfIndexRange {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
boolean result1 = arrayList.remove(String.valueOf(6));
String result2 = arrayList.remove(10);
System.out.println("testRemoveIndexAndValue(): " + result1);
System.out.println("testRemoveIndexAndValue(): " + result2);
System.out.println("testRemoveIndexAndValue(): " + arrayList);
}
4.3 get【获取元素值】
private String get(int index) throws mArrayListOutOfIndexRange {
if (index - 1 < 0 || index - 1 > this.size) {
throw new mArrayListOutOfIndexRange(index + "越界");
} else {
return this.array[index - 1];
}
}
private static void testGet() throws mArrayListOutOfIndexRange {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
String result = arrayList.get(1);
System.out.println("testGet(): " + result);
}
4.4 set【设置元素值】
private void set(int index, String e) throws mArrayListOutOfIndexRange {
if (index - 1 < 0 || index - 1 > this.size) {
throw new mArrayListOutOfIndexRange(index + "越界");
} else {
this.array[index - 1] = e;
}
}
private static void testSet() throws mArrayListOutOfIndexRange {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
arrayList.set(1, String.valueOf(11));
System.out.println("testSet():" + arrayList);
}
4.5 contains【元素是否存在】
private boolean contains(String e) {
for (int i = 0; i < this.size; i++) {
if (this.array[i].equals(e)) {
return true;
}
}
return false;
}
private static void testContains() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
System.out.println("testContains(): " + arrayList.contains(String.valueOf(1)));
}
private static void testIndexOf() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
System.out.println(arrayList.indexOf("testIndexOf(): " + String.valueOf(1)));
}
4.6 indexOf【正序查下标】
private int indexOf(String e) {
for (int i = 0; i < this.size; i++) {
if (this.array[i].equals(e)) {
return i + 1;
}
}
return -1;
}
private static void testIndexOf() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
System.out.println(arrayList.indexOf("testIndexOf(): " + String.valueOf(1)));
}
4.7 lastIndexOf【逆序查下标】
private int lastIndexOf(String e) {
for (int i = this.size - 1; i >= 0; i--) {
if (this.array[i].equals(e)) {
return i + 1;
}
}
return -1;
}
private static void testLastIndexOf() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
arrayList.addLast(String.valueOf(1));
System.out.println("testLastIndexOf(): " + arrayList.lastIndexOf(String.valueOf(1)));
}
4.8 clear【清空】
private void clear() {
this.size = 0;
}
private static void testClear() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
arrayList.clear();
System.out.println("testClear(): " + arrayList);
}
4.9 size【获取长度】
private int size() {
return this.size;
}
private static void testSize() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
System.out.println("testSize(): " + arrayList.size);
}
4.10 isEmpty【是否为空】
private boolean isEmpty() {
return this.size == 0;
}
private static void testIsEmpty() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
System.out.println("testIsEmpty(): " + arrayList.isEmpty());
}
4.11 越界异常类
class mArrayListOutOfIndexRange extends Exception {
/*
由于 mArrayListOutOfIndexRange 继承的是 Exception 受查异常所以需要手动解决问题
如果继承的是 RuntiemException 非受查异常,所以不需要手动解决
这里继承的是 受查异常,所以需要在调用到这个异常的函数都需要手动抛出异常类
*/
public mArrayListOutOfIndexRange(String message) {
super(message);
}
}
5.完整代码【myArrayList.java】
class mArrayListOutOfIndexRange extends Exception {
/*
由于 mArrayListOutOfIndexRange 继承的是 Exception 受查异常所以需要手动解决问题
如果继承的是 RuntiemException 非受查异常,所以不需要手动解决
这里继承的是 受查异常,所以需要在调用到这个异常的函数都需要手动抛出异常类
*/
protected mArrayListOutOfIndexRange(String message) {
super(message);
}
}
public class myArrayList {
private String[] array;
private int size;
private static int capacity = 5;
private myArrayList() {
this.array = new String[capacity];
this.size = 0;
}
// 扩容
private void realloc() {
capacity *= 2;
String[] newArray = new String[capacity];
for (int i = 0; i < this.size; i++) {
newArray[i] = this.array[i];
}
// this.array 的引用指向 newArray 后之前引用指向内容由于缺少引用,就会被垃圾回收机制(GC)回收
this.array = newArray;
}
// 尾插
private void addLast(String e) {
if (this.size >= capacity) {
realloc();
}
this.array[this.size++] = e;
}
// 任意位置添加
private void addIndex(int index, String e) throws mArrayListOutOfIndexRange {
if (index <= 0 || index - 1 > this.size) {
throw new mArrayListOutOfIndexRange(index + "下标越界");
} else {
if (this.size >= capacity) {
realloc();
}
for (int i = this.size - 1; i >= index; i--) {
this.array[i + 1] = this.array[i];
}
this.array[index - 1] = e;
++this.size;
}
}
// 按下标删除
private String remove(int index) throws mArrayListOutOfIndexRange {
if (index <= 0 || index - 1 > this.size) {
throw new mArrayListOutOfIndexRange(index + "越界");
} else {
/*
index-1 是为了消除下标起始 0 开始
this.size-1 是因为一处一个元素后数组长度要减一
*/
String result = this.array[index - 1];
for (int i = index - 1; i < this.size - 1; i++) {
this.array[i] = this.array[i + 1];
}
--this.size;
return result;
}
}
// 按值删除【删除第一次出现的元素】
private boolean remove(String e) {
for (int i = 0; i < this.size - 1; i++) {
if (this.array[i].equals(e)) {
for (int j = i; j < this.size - 1; j++) {
this.array[j] = this.array[j + 1];
}
--this.size;
return true;
}
}
return false;
}
private String get(int index) throws mArrayListOutOfIndexRange {
if (index - 1 < 0 || index - 1 > this.size) {
throw new mArrayListOutOfIndexRange(index + "越界");
} else {
return this.array[index - 1];
}
}
private void set(int index, String e) throws mArrayListOutOfIndexRange {
if (index - 1 < 0 || index - 1 > this.size) {
throw new mArrayListOutOfIndexRange(index + "越界");
} else {
this.array[index - 1] = e;
}
}
private boolean contains(String e) {
for (int i = 0; i < this.size; i++) {
if (this.array[i].equals(e)) {
return true;
}
}
return false;
}
private int indexOf(String e) {
for (int i = 0; i < this.size; i++) {
if (this.array[i].equals(e)) {
return i + 1;
}
}
return -1;
}
private int lastIndexOf(String e) {
for (int i = this.size - 1; i >= 0; i--) {
if (this.array[i].equals(e)) {
return i + 1;
}
}
return -1;
}
private void clear() {
this.size = 0;
}
private int size() {
return this.size;
}
private boolean isEmpty() {
return this.size == 0;
}
@Override
public String toString() {
StringBuffer stringBuffer = new StringBuffer("[");
for (int i = 0; i < this.size; i++) {
stringBuffer.append(this.array[i]);
if (i < this.size - 1) {
stringBuffer.append(", ");
}
}
stringBuffer.append("]");
String result = stringBuffer.toString();
return result;
}
private static void testAddLast() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
System.out.println("testAddLast(): " + arrayList);
}
private static void testAddIndex() throws mArrayListOutOfIndexRange {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addIndex(i, String.valueOf(i));
}
System.out.println("testAddIndex(): " + arrayList);
}
private static void testRemoveIndexAndValue() throws mArrayListOutOfIndexRange {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
boolean result1 = arrayList.remove(String.valueOf(6));
String result2 = arrayList.remove(10);
System.out.println("testRemoveIndexAndValue(): " + result1);
System.out.println("testRemoveIndexAndValue(): " + result2);
System.out.println("testRemoveIndexAndValue(): " + arrayList);
}
private static void testGet() throws mArrayListOutOfIndexRange {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
String result = arrayList.get(1);
System.out.println("testGet(): " + result);
}
private static void testSet() throws mArrayListOutOfIndexRange {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
arrayList.set(1, String.valueOf(11));
System.out.println("testSet():" + arrayList);
}
private static void testContains() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
System.out.println("testContains(): " + arrayList.contains(String.valueOf(1)));
}
private static void testIndexOf() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
System.out.println(arrayList.indexOf("testIndexOf(): " + String.valueOf(1)));
}
private static void testLastIndexOf() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
arrayList.addLast(String.valueOf(1));
System.out.println("testLastIndexOf(): " + arrayList.lastIndexOf(String.valueOf(1)));
}
private static void testClear() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
arrayList.clear();
System.out.println("testClear(): " + arrayList);
}
private static void testSize() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
System.out.println("testSize(): " + arrayList.size);
}
private static void testIsEmpty() {
myArrayList arrayList = new myArrayList();
for (int i = 1; i <= 10; i++) {
arrayList.addLast(String.valueOf(i));
}
System.out.println("testIsEmpty(): " + arrayList.isEmpty());
}
public static void main(String[] args) throws mArrayListOutOfIndexRange {
// 放置测试用例
}
}
6.部分逻辑思路解析
6.1 插入算法思路
- 如果指定插入位置不合理,抛出异常
- 如果线性表长度大于等于数组长度,则动态增加容量
- 从最后一个元素开始向前遍历到第 index 个位置,分别将它们都向后移动一个位置
- 将要插入元素填入位置 index 处
- 表长加 1
6.2 删除算法思路
- 如果删除位置 index 不合理,抛出异常
- 取出删除元素
- 从删除元素位置 index 开始遍历到最后一个元素,分别将它们都向前移动一格位置
- 表长减 1
7. 线性表顺序存储结构的优缺点
现在我们来分析一下,插入和删除的 间复杂度。
先来看最好的情况,如果元素要插入到最后一个位置或者删除最后一个元素,此时时时间复杂度为 O(1) ,因为不需要移动元素的,就如同来了一个新人要正常排队,当然是排在最后,如果此时他又不想排了,那么他一个人离开就好了,不影响任何人。
最坏的情况呢,如果元素要插入到第一个位置或者删除第一个元素,此时时间复杂度是多少? 那就意味着要移动所有的元素向后或者向前,所以这个时间复杂度为O(n)
至于平均的情况,由于元素插入到第 i 个位置,或删除第 i 个元素 需要移动 n-i 个元素 根据概率原理,每个位置插入或删除元素的可能性是相同的,也就说位置靠前,移动元素多,位置靠后,移动元素少。最终平均移动次数和最中间的那个元素移动次数相等,为(n-1)/2。
时间复杂度的推导,可以得出,平均时间复杂度还是 O(n)
这说明什么? 线性表的顺序存储结构,在存,读数据时,不管是 置,时间复杂度都是 O(1) 而插入或删除时,时间复杂度都是 O(n) 。这就说明:它比较适合元素个数不大变化,而更多是存取数据的应用。当然,它的优缺点还不只这些…
优点 | 缺点 |
---|---|
无须为表中元素之间的逻辑关系而增加额外的存储空间 | 插入和删除操作需要移动大量元素 |
可以快速地存取表中任意位置的元素 | 造成存储空间的“碎片” |
当线性表长度变化较大的时候,难以确定存储空间的容量 |
8.泛型顺序表
仅提供稍微难一点的增加和删除操作,其余操作就不演示代码和测试用例
package dataStructure;
import java.util.Arrays;
/**
* Created by: cxf
* Description:
* User: 19696
* Date: 2021-08-30
* Time: 15:40
*/
class genericsArrayListIndexOutOfRange extends RuntimeException {
protected genericsArrayListIndexOutOfRange(String message) {
super(message);
}
}
public class genericsArrayList<T> {
private T[] elem;
private int usedSize;
private static int intCapacity = 5;
private genericsArrayList() {
this.elem = (T[]) new Object[intCapacity];
this.usedSize = 0;
}
// 扩容
private void realloc() {
intCapacity *= 2;
this.elem = Arrays.copyOf(this.elem, intCapacity);
}
// 增
private void addLast(T e) {
if (this.usedSize >= intCapacity) {
realloc();
}
this.elem[this.usedSize++] = e;
}
private void addIndex(int index, T e) {
if (this.usedSize >= intCapacity) {
realloc();
}
if (index > this.usedSize) {
throw new genericsArrayListIndexOutOfRange(index + "越界");
} else {
for (int i = this.usedSize - 1; i >= index; i++) {
this.elem[i + 1] = this.elem[i];
}
this.elem[index] = e;
++this.usedSize;
}
}
// 删
private void remove(int index) {
if (index > this.usedSize) {
throw new genericsArrayListIndexOutOfRange(index + "越界");
} else {
for (int i = index; i < this.usedSize - 1; i++) {
this.elem[i] = this.elem[i + 1];
}
--this.usedSize;
}
}
private void remove(T e) {
for (int i = 0; i < this.usedSize; i++) {
if (e.equals(this.elem[i]) || this.elem[i] == e) {
for (int j = i; j < this.usedSize - 1; j++) {
this.elem[j] = this.elem[j + 1];
}
--this.usedSize;
break;
}
}
}
@Override
public String toString() {
StringBuffer stringBuffer = new StringBuffer("[");
for (int i = 0; i < this.usedSize; i++) {
if (i < this.usedSize - 1) {
stringBuffer.append(this.elem[i] + ", ");
}
}
stringBuffer.append(this.elem[this.usedSize - 1] + "]");
return stringBuffer.toString();
}
private static void testAdd() {
genericsArrayList<Integer> arrayList1 = new genericsArrayList<>();
for (int i = 0; i < 10; i++) {
arrayList1.addLast(i);
}
System.out.println("addLast():" + arrayList1);
genericsArrayList<Integer> arrayList2 = new genericsArrayList<>();
for (int i = 0; i < 10; i++) {
arrayList2.addIndex(i, i);
}
System.out.println("addIndex():" + arrayList2);
}
private static void testRemove() {
genericsArrayList<Integer> arrayList = new genericsArrayList<>();
for (int i = 0; i < 10; i++) {
arrayList.addLast(i);
}
for (int i = 0; i < 5; i++) {
arrayList.remove(Integer.valueOf(i));
}
System.out.println(arrayList);
}
public static void main(String[] args) {
// 放置测试用例
}
}