什么是顺序表?
顺序表是一种线性表的实现方式,是一种基础的数据结构,其中的元素按照一定的顺序依次存放在一段连续的内存空间中。顺序表通常使用数组来实现,因此在内存中占据一段连续的存储空间。
顺序表的特点包括:
-
连续存储: 顺序表中的元素在内存中是连续存储的,这样可以通过元素的下标快速访问任意位置的元素。
-
随机访问: 由于顺序表中的元素是连续存储的,因此可以通过下标直接访问指定位置的元素,时间复杂度为 O(1)。
-
固定大小: 在使用数组实现的顺序表中,通常需要提前指定一个固定的大小,即最大容量,因此顺序表的大小是固定的,无法动态改变。
-
插入和删除效率低: 当需要在顺序表中插入或删除元素时,需要移动后续元素的位置,因此插入和删除操作的效率较低,时间复杂度为 O(n)。
-
适用场景: 顺序表适用于需要频繁访问元素、元素数量不经常变化、对内存空间使用要求较高的场景。
顺序表的存储结构
(1) 用数组存储数据
typedef int ElemType; // 定义元素类型
// 顺序表结构体定义
#define MAXSIZE 100 // 定义顺序表的最大长度
typedef struct {
ElemType elem[MAXSIZE]; // 存储元素的数组
int length; // 当前顺序表长度
} SqList;
(2)用指针存储数据
typedef int ElemType;
typedef struct {
ElemType* elem;
int length;
}SqList;
基本操作的实现
定义函数结果状态码及数据类型
typedef int Status; // 定义状态类型
#define OK 1
#define ERROR 0
#define OVERFLOW -2
初始化
简介:为其分配内存空间并将长度置为0。时间复杂度:O(1) 空间复杂度:O(1)
// 初始化顺序表
Status InitList(SqList& L) {
L.elem = (ElemType*)malloc(sizeof(ElemType) * MAXSIZE); // 分配内存空间
if (!L.elem) {
exit(OVERFLOW); // 内存分配失败
}
L.length = 0; // 初始化长度为0
return OK;
}
取值
简介:获取顺序表中指定位置的元素值。时间复杂度:O(1) 空间复杂度:O(1)
// 获取指定位置的元素
Status GetElem(SqList L, int i, ElemType* e) {
if (i<1 || i>L.length) {
return ERROR; // 位置错误
}
*e = L.elem[i - 1]; // 获取元素值
return OK;
}
销毁顺序表
简介:释放顺序表所占用的内存空间。 时间复杂度:O(1) 空间复杂度:O(1)
// 销毁线性表
int DestoryList(SqList& L) {
if (L.elem) {
free(L.elem); // 释放内存
return 1;
}
else {
return 0;
}
}
清空顺序表
简介:将顺序表的长度置为0,但不释放内存空间。时间复杂度:O(1) 空间复杂度:O(1)
// 清空顺序表
Status ClearList(SqList& L) {
L.length = 0; // 将长度置为0
return OK;
}
获取顺序表的长度
简介:返回顺序表的当前长度。时间复杂度:O(1) 空间复杂度:O(1)
// 获取顺序表长度
int GetLength(SqList L) {
return L.length;
}
判断顺序表是否为空
简介:检查顺序表是否为空。时间复杂度:O(1) 空间复杂度:O(1)
// 判断顺序表是否为空
int IsEmpty(SqList L) {
if (L.length == 0) {
return 1;
}
else {
return 0;
}
}
元素查找
简介:在顺序表中查找指定元素的位置。。时间复杂度:O(n) 空间复杂度:O(1)
// 查找指定元素的位置(从1开始计数)
int LocateElem(SqList L, ElemType e) {
for (int i = 0; i < L.length; i++) {
if (L.elem[i] == e) {
return i + 1; // 返回位置
}
}
return 0; // 未找到元素
}
插入元素
简介:在顺序表的指定位置插入一个新元素。时间复杂度:O(n) 空间复杂度:O(1)
算法概括:从插入位置开始,将后续元素依次向后移动,为新元素腾出位置,并插入新元素。
// 向指定位置插入元素
Status ListInsert(SqList& L, int i, ElemType e) {
if ((i < 1) || (i > L.length + 1)) {
return ERROR; // 位置错误
}
if (L.length == MAXSIZE) {
return ERROR; // 顺序表已满
}
for (int j = L.length - 1; j >= i - 1; j--) {
L.elem[j + 1] = L.elem[j]; // 元素后移
}
L.elem[i - 1] = e; // 插入元素
L.length++; // 长度加1
return OK;
}
删除元素
简介:从线性表中删除指定位置的元素。时间复杂度:O(n) 空间复杂度:O(1)
算法概括:从删除位置开始,将后续元素依次向前移动,覆盖删除位置的元素。
// 删除指定位置的元素
Status ListDelete(SqList& L, int i) {
if ((i < 1) || (i > L.length)) {
return ERROR; // 位置错误
}
for (int j = i; j < L.length - 1; j++) {
L.elem[j - 1] = L.elem[j]; // 元素前移
}
L.length--; // 长度减1
return OK;
}
反转顺序表
简介:反转线性表中元素的顺序。时间复杂度:O(n) 空间复杂度:O(1)
算法概括:设置两个指针,分别指向顺序表的起始位置和结束位置,逐步交换两个指针所指向的元素,直到两个指针相遇。
// 反转顺序表
void SqListReverse(SqList& L) {
int i = 0; // 左边待交换的元素位置
int j = L.length - 1; // 右边待交换的元素位置
while (i < j) {
// 交换元素值
ElemType temp = L.elem[i];
L.elem[i] = L.elem[j];
L.elem[j] = temp;
i++; // 继续向右移
j--; // 继续向左移
}
}
删除指定元素
简介:从线性表中删除所有出现的指定元素。
(1)未要求元素间的相对位置不变。时间复杂度:O(n) 空间复杂度:O(1)
算法概括:设置两个指针,分别从顺序表的头部和尾部开始向中间移动,当左指针指向的元素等于指定元素且右指针指向的元素不等于指定元素时,交换两个元素,直到两个指针相遇。
// 删除所有出现的指定元素
void Delete(SqList& L, ElemType item) {
int i = 0;
int j = L.length - 1; // 设置数组低、高端指针(下标)
while (i < j) {
while (i < j && L.elem[i] != item) {
i++; // 若值不为item,左移指针
}
while (i < j && L.elem[j] == item) {
j--; // 若右端元素为item,右移指针
}
if (i < j) {
L.elem[i++] = L.elem[j--]; // 左指针右移,右指针左移
}
}
L.length = i; // 更新长度
}
(2)要求元素间的相对位置不变。时间复杂度:O(n) 空间复杂度:O(1)
算法概括:初始化计数器为0,遍历顺序表,对于每个元素,如果等于指定元素则增加计数器,否则向前移动元素位置,最后根据计数器更新顺序表的长度。
void deleteAllOccurrences(SqList &L, int n, ElemType item) {
int count = 0; // 记录出现的指定元素的次数
for (int i = 0; i < n; i++) {
if (L.elem[i] == item) {
count++; // 统计出现的次数
} else {
L.elem[i - count] = L.elem[i]; // 将非指定元素向前移动
}
}
L.length = n - count; // 更新顺序表的长度
}
有序列表插入元素
简介:在有序顺序表中插入一个新元素,保持顺序表有序。时间复杂度:O(n) 空间复杂度:O(1)
算法概括:从顺序表的尾部开始,逐个向前比较元素值,将大于待插入元素的元素后移一个位置,直到找到第一个小于等于待插入元素的位置,然后插入新元素。
// 向有序列表插入元素(保持有序)
Status ListInsert(SqList& L, int x) {
if (L.length >= MAXSIZE) {
return ERROR; // 判断空间是否已满
}
int i = 0;
for (i = L.length - 1; L.elem[i] > x; i--) {
L.elem[i + 1] = L.elem[i]; // 元素后移
}
L.elem[i + 1] = x; // 插入元素
L.length++; // 长度加1
return OK;
}
删除指定数量的元素
简介:从线性表的开头删除指定数量的元素。时间复杂度:O(n) 空间复杂度:O(1)
算法概括:将从第n个元素开始到最后一个元素依次向前移动n个位置,然后将线性表的长度减去n。
// 删除指定数量的元素
void DeleteData(SqList& L, int n) {
if (L.length <= n) {
L.length = 0; // 长度小于等于n,直接清空列表
}
else {
for (int i = n; i < L.length; i++) {
L.elem[i - n] = L.elem[i]; // 元素前移n个位置
}
L.length = L.length - n; // 更新长度
}
}
结束语
通过本篇博客,我们深入了解了顺序表的基本操作以及一个用于删除所有出现指定元素的实用函数。顺序表作为一种基础的线性表实现方式,在实际编程中应用广泛。我们了解到顺序表的特点包括连续存储、随机访问、固定大小等,并学习了如何操作顺序表进行元素的插入、删除、查找等基本操作。
同时,我们也注意到了顺序表在插入和删除操作上的低效性,这使得在需要频繁插入和删除操作的场景下,可能需要考虑其他更适合的数据结构。因此,在实际应用中,我们需要根据具体场景和需求选择最合适的数据结构来提高程序的效率和性能。
希望本篇博客能够帮助读者更深入地理解顺序表的概念和操作,并为日后的编程实践提供一定的指导和参考。