文章目录
顺序表的文字介绍
顺序表的概念
顺序表,顾名思义,就是一种按照顺序排列的线性表。线性表是一种非常基础且重要的数据结构,在计算机科学中有着广泛的应用。常见的线性表:顺序表、链表、栈、队列、字符串等等
顺序表通常是用一段连续的存储单元来依次存储线性表中的数据元素,这使得顺序表在物理位置上也是相邻的。由于这个特性,顺序表允许我们通过计算元素的位置来直接访问它,这种访问方式被称为“随机访问”或“直接访问”。例如,如果我们知道顺序表的起始地址和每个元素所占的存储空间大小,那么我们就可以通过简单的计算找到任何一个元素的位置。
静态与动态顺序表
动态顺序表和静态顺序表是数据结构中的两种重要线性表实现方式,它们在存储和管理数据方面有着明显的区别。
静态顺序表
定义与特点:
静态顺序表使用定长的数组来存储数据元素,这意味着在声明时就需要确定数组的大小,且之后无法改变。
它类似于一个固定大小的容器,一旦装满就无法再加入新的元素,除非删除现有元素腾出空间。
由于静态顺序表的大小在编译时就已确定,因此其内存空间是连续分配的,在函数栈上(静态区)进行分配,随着函数调用的结束,这块内存区域会被系统自动回收。
//静态顺序表
#define N 100
struct SeqList
{
SLDataType a[N];
int size;
};
优缺点:
优点:操作简单,不用手动释放内存空间,不存在内存泄漏的风险。
缺点:灵活性差,当数据量超出预定大小时,无法扩展容量,可能导致数据溢出;若预定空间过大,则可能造成内存浪费。
动态顺序表
定义与特点:
动态顺序表使用动态开辟的内存空间来存储数据元素,其大小可以根据需要动态变化。
它通过指针和动态内存分配函数(如malloc、realloc)来管理内存,可以根据数据的增长或减少自动调整容量。
动态顺序表的内存空间在堆上分配,不会随着函数调用的结束被系统自动回收,需要程序员主动释放(使用free函数)。
扩容机制:
当动态顺序表的元素数量达到当前容量上限时,会自动进行扩容操作。扩容通常是通过重新分配一块更大的内存空间,并将原有数据复制到新空间中来实现的。扩容的大小通常是原容量的两倍或更多,以减少扩容操作的频率。
//动态顺序表
typedef int SLDataType;
struct SqeList {
SLDataType* a;
int size;
int capacity;
};
优缺点:
优点:灵活性高,可以根据需要动态调整容量,避免内存浪费;操作灵活,可以高效地处理大量数据。
缺点:需要程序员手动管理内存(分配和释放),增加了编程的复杂性和出错的可能性;扩容操作需要额外的时间和内存开销。
以下代码实现的是动态顺序表
代码实现
顺序表的定义
// 定义动态顺序表中存储的数据类型,这里假设是int类型
typedef int SLDataType;
// 定义动态顺序表的结构体
typedef struct SqeList {
// 指向动态分配数组的指针,用于存储顺序表中的数据
SLDataType* a;
// 当前顺序表中存储的数据的数量
int size;
// 当前动态分配的数组的总容量
int capacity;
}SL; // SL是SqeList的别名,方便后续使用
顺序表的初始化
// 顺序表的初始化函数
void SLInit(SL* ps) {
ps->a = NULL; // 初始化顺序表的指针为NULL,表示顺序表当前为空
ps->size = ps->capacity = 0; // 初始化顺序表的当前大小和容量为0,表示顺序表为空且没有分配内存空间
}
顺序表的销毁
// 顺序表的销毁函数
void SLDestroy(SL* ps) {
assert(ps); // 断言,确保传入的顺序表指针不为空
free(ps->a); // 释放顺序表所占用的内存,如果是动态分配的数组,直接释放,不需要遍历释放每个元素
ps->a = NULL; // 将顺序表的指针置为NULL,避免野指针问题
ps->size = ps->capacity = 0; // 将顺序表的当前大小和容量都置为0,表示顺序表已被销毁
}
检查顺序表的容量
// 检查顺序表的容量函数
void SLCheckCapacity(SL* ps) {
assert(ps); // 断言,确保传入的顺序表指针不为空
// 如果顺序表的当前大小等于其容量,说明顺序表已满,需要进行扩容
if (ps->capacity == ps->size) {
// 计算新的容量,如果当前容量为0,则初始化为4;否则,将容量翻倍
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
// 使用realloc函数重新分配内存空间,扩大顺序表的容量
SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType));
// 如果realloc函数返回NULL,说明内存分配失败,打印错误信息并退出程序
if (tmp == NULL) {
perror("realloc fail");
exit(1);
}
// 更新顺序表的容量
ps->capacity = newCapacity;
// 更新顺序表的指针,指向新的内存空间
ps->a = tmp;
}
// 注意:此函数只是检查并可能扩容顺序表,不会向顺序表中添加元素
// 因此,不需要执行添加元素的代码,如下两行已被注释掉:
// ps->a[ps->size] = x; // 添加元素到顺序表的末尾(此行代码已注释)
// ps->size++; // 顺序表的大小加1(此行代码已注释)
}
顺序表的尾插
// 顺序表的尾插函数
void SLPushBack(SL* ps, SLDataType x) {
assert(ps); // 断言,确保传入的顺序表指针不为空
// 在进行插入操作之前,先检查顺序表的容量是否足够,只要是插入都要先检查容量够不够
SLCheckCapacity(ps);
// 将新元素插入到顺序表的末尾
ps->a[ps->size] = x;
// 顺序表的大小加1
ps->size++;
}
顺序表的头插
// 顺序表的头插函数
void SLPushFront(SL* ps, SLDataType x) {
assert(ps); // 断言,确保传入的顺序表指针不为空
// 在进行插入操作之前,先检查顺序表的容量是否足够
SLCheckCapacity(ps);
// 将顺序表中的旧数据往后挪动一位,为新元素腾出空间
for (int i = ps->size; i > 0; i--) {
ps->a[i] = ps->a[i - 1];
}
// 将新元素插入到顺序表的头部
ps->a[0] = x;
// 顺序表的大小加1
ps->size++;
}
顺序表的头删
// 顺序表的头删函数
void SLPopFront(SL* ps) {
assert(ps); // 断言,确保传入的顺序表指针不为空
assert(ps->size); 断言,确保顺序表不为空,即至少有一个元素可以删除
// 将顺序表中的元素从第二个开始,依次往前挪动一位,覆盖掉第一个元素
for (int i = 1; i < ps->size; i++) {
ps->a[i - 1] = ps->a[i];
}
// 顺序表的大小减1
ps->size--;
}
顺序表的尾删
// 顺序表的尾删函数
void SLPopBack(SL* ps) {
assert(ps); // 断言,确保传入的顺序表指针不为空
assert(ps->size); // 断言,确保顺序表不为空,即至少有一个元素可以删除
// 直接将顺序表的大小减1,即可实现尾删操作
// 因为顺序表的尾部元素在逻辑上被“移除”了,实际上并未真正从内存中删除
ps->size--;
}
指定位置之前插入顺序表
// 指定位置之前插入元素的顺序表函数
void SLInsert(SL* ps, int pos, SLDataType x) {
assert(ps); // 断言,确保传入的顺序表指针不为空
assert(pos >= 0 && pos <= ps->size); // 断言,确保插入位置合法,即在顺序表的当前大小范围内
SLCheckCapacity(ps); // 检查顺序表的容量,如果不够则进行扩容
// 将pos位置及其之后的数据往后挪动一位,为新元素腾出空间
for (int i = ps->size; i > pos; i--) {
ps->a[i] = ps->a[i - 1];
}
将新元素插入到指定位置pos
ps->a[pos] = x;
// 顺序表的大小加1
ps->size++;
}
删除指定位置数据
// 删除指定位置数据的顺序表函数
void SLErase(SL* ps, int pos) {
assert(ps); // 断言,确保传入的顺序表指针不为空
assert(pos >= 0 && pos < ps->size); // 断言,确保删除位置合法,即在顺序表的当前大小范围内
// 将pos位置以后的数据往前挪动一位,覆盖掉要删除的元素
for (int i = pos; i < ps->size - 1; i++) {
ps->a[i] = ps->a[i + 1];
}
// 顺序表的大小减1
ps->size--;
}
打印顺序表数据
// 打印顺序表数据的函数
void SLPrint(SL* ps) {
assert(ps); // 断言,确保传入的顺序表指针不为空
// 遍历顺序表,打印每个元素
for (int i = 0; i < ps->size; i++) {
printf("%d ", ps->a[i]);
}
printf("\n"); // 打印完所有元素后换行
}
测试
以下只是我个人的测试,大家测试之前可以把注释看一下,然后合理使用即可
//测试
void test() {
SL sl;// 声明一个顺序表变量
SLInit(&sl);//使用之前记得初始化
SLPushBack(&sl, 10);
SLPushBack(&sl, 20);
SLPushBack(&sl, 30);
SLPushBack(&sl, 40);
SLPushBack(&sl, 50);
SLPrint(&sl);
SLPushFront(&sl, 5);
SLPushFront(&sl, 1);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
SLPopBack(&sl);
SLPrint(&sl);
SLInsert(&sl, 3, 70);//注意这里的3指的是下标,也就是第四个元素
SLPrint(&sl);
SLInsert(&sl, 1, 80);
SLPrint(&sl);
SLErase(&sl, 4);//注意这里的4指的是下标,也就是第五个元素
SLPrint(&sl);
SLErase(&sl, 3);
SLPrint(&sl);
}
完整代码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//动态顺序表的定义
typedef int SLDataType;
typedef struct SqeList {
SLDataType* a;
int size;
int capacity;
}SL;
//顺序表的初始化
void SLInit(SL* ps) {
ps->a = NULL;
ps->size = ps->capacity = 0;
}
//顺序表的销毁
void SLDestroy(SL* ps) {
assert(ps);
free(ps->a);//如果是数组,直接释放,不需要遍历释放
ps->a = NULL;
ps->size = ps->capacity = 0;
}
//检查顺序表的容量
void SLCheckCapacity(SL* ps) {
assert(ps);
if (ps->capacity == ps->size) {
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType));
if (tmp == NULL) {
perror("realloc fail");
exit(1);
}
ps->capacity = newCapacity;
ps->a = tmp;
}
}
//顺序表的尾插
void SLPushBack(SL* ps, SLDataType x) {
assert(ps);
SLCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
//顺序表的头插
void SLPushFront(SL* ps, SLDataType x) {
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size; i > 0; i--) {
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->size++;
}
//顺序表的头删
void SLPopFront(SL* ps) {
assert(ps);
assert(ps->size);
for (int i = 1; i < ps->size; i++) {
ps->a[i - 1] = ps->a[i];
}
ps->size--;
}
//顺序表的尾删
void SLPopBack(SL* ps) {
assert(ps);
assert(ps->size);
ps->size--;
}
//指定位置之前插入顺序表
void SLInsert(SL* ps, int pos, SLDataType x) {
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size; i > pos; i--) {
ps->a[i] = ps->a[i - 1];
}
ps->a[pos] = x;
ps->size++;
}
//删除指定位置数据
void SLErase(SL* ps, int pos) {
assert(ps);
assert(pos >= 0 && pos < ps->size);
//pos以后的数据往前挪动一位
for (int i = pos; i < ps->size-1; i++) {
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
//打印顺序表数据
void SLPrint(SL* ps) {
assert(ps);
for (int i = 0; i < ps->size; i++) {
printf("%d ", ps->a[i]);
}
printf("\n");
}
//测试
void test() {
SL sl;
SLInit(&sl);//使用之前记得初始化
SLPushBack(&sl, 10);
SLPushBack(&sl, 20);
SLPushBack(&sl, 30);
SLPushBack(&sl, 40);
SLPushBack(&sl, 50);
SLPrint(&sl);
SLPushFront(&sl, 5);
SLPushFront(&sl, 1);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
SLPopBack(&sl);
SLPrint(&sl);
SLInsert(&sl, 3, 70);
SLPrint(&sl);
SLInsert(&sl, 1, 80);
SLPrint(&sl);
SLErase(&sl, 4);
SLPrint(&sl);
SLErase(&sl, 3);
SLPrint(&sl);
}
int main() {
test();
return 0;
}