顺序表是一种基本的数据结构,它在C语言中通常使用数组来实现。顺序表是一种线性表的物理存储结构,其特点是数据元素的逻辑顺序和物理顺序相同,即表中第i个位置的元素对应数组的第i个元素。
顺序表的结构
结构体第一个元素应该写数组,其次是我们需要该顺序表实现的功能;
例如:一个可以容纳1000整形并且可以知道有效数据个数和容量大小的的顺序表。
typedef struct SlDatatype//数据表名称
{
int arr[1000];//定义顺序表可容纳的数据个数
int size; //有效数据个数
int capacity //容量大小
}
顺序表的特点
- 随机访问:顺序表支持通过索引快速访问任意位置的元素。
- 动态分配:在C语言中,通常使用指针和动态内存分配(如
malloc
和realloc
)来创建可变长度的顺序表。 - 连续存储:顺序表中的元素在内存中是连续存储的。
顺序表和数组的区别?
顺序表的底层结构是数组,对数组的封装,实现了常⽤的增删改查等接⼝。
顺序表的分类
静态顺序表
数组空间确定,容量确定,预先给定的空间太大会造成空间浪费,太小会导致数据存储不完。
#define N 1000
typedef char SLDataType;
//静态顺序表
typedef struct seqlist
{
SLDataType arr[N];
int size;//有效数据个数
}SL;
动态顺序表
将静态顺序表中首元素由数组名变成了数组地址,可以利用malloc,realloc函数来为表中数组动态申请空间,可以更高效的利用空间。
//动态顺序表
typedef struct seqList
{
SLDataType* arr;
int size;//有效数据个数
int capacity;//容量大小
}SL;
下面详细的介绍动态顺序表常用功能的使用方法
初始化:使用malloc
或calloc
函数动态分配内存空间,初始化顺序表。例如:
typedef struct {
int *data; // 指向动态分配数组的指针
int length; // 顺序表的当前长度
int capacity; // 顺序表的最大容量
} SeqList;
void initSeqList(SeqList *list, int initialCapacity) //用于申请空间的函数
{
list->data = (int *)malloc(initialCapacity * sizeof(int));//为函数申请初始空间
if (list->data == NULL) //如果申请失败,则返回错误报告并退出程序
{
printf("Memory allocation failed.\n");
exit(1);
}
list->length = 0;//刚申请空间,目前一个数据都没有,初始化顺序表长度为0;
list->capacity = initialCapacity;//显示顺序表容量
}
销毁:使用free
函数释放动态分配的内存空间。
void freeSeqList(SeqList *list) {
free(list->data);
list->data = NULL;
list->length = 0;
list->capacity = 0;
}
插入元素:在顺序表的指定位置插入元素,如果空间不足,需要使用realloc
函数扩大数组容量。
void insert(SeqList *list, int index, int value)//用于给顺序表插入我们所需要记录的内容的函数
{
if (index < 0 || index > list->length)//例行报错
{
printf("Index out of bounds.\n");
return;
}
if (list->length == list->capacity)//如果原数组内存不够,则继续申请空间
{
int newCapacity = list->capacity * 2;
int *newData = (int *)realloc(list->data, newCapacity * sizeof(int));
if (newData == NULL) {
printf("Memory reallocation failed.\n");
return;
}
list->data = newData;
list->capacity = newCapacity;
}
for (int i = list->length; i > index; i--)//将原数据向左移动一位
{
list->data[i] = list->data[i - 1];
}
list->data[index] = value;
list->length++;
}
删除元素:从顺序表中删除指定位置的元素。
void remove(SeqList *list, int index)
{
if (index < 0 || index >= list->length)
{
printf("Index out of bounds.\n");
return;
}
for (int i = index; i < list->length - 1; i++)
{
list->data[i] = list->data[i + 1];
}
list->length--;
}
查找元素:在顺序表中查找一个元素,返回其位置。
int find(SeqList *list, int value) {
for (int i = 0; i < list->length; i++) {
if (list->data[i] == value) {
return i;
}
}
return -1;
}
更新元素:更新顺序表中指定位置的元素值。
void update(SeqList *list, int index, int newValue) {
if (index < 0 || index >= list->length) {
printf("Index out of bounds.\n");
return;
}
list->data[index] = newValue;
}
打印顺序表:打印顺序表中的所有元素。
void printSeqList(SeqList *list) {
for (int i = 0; i < list->length; i++) {
printf("%d ", list->data[i]);
}
printf("\n");
}
扩容:当顺序表的容量不足以容纳更多元素时,需要扩容。
void ensureCapacity(SeqList *list, int newCapacity) {
int *newData = (int *)realloc(list->data, newCapacity * sizeof(int));
if (newData == NULL) {
printf("Memory reallocation failed.\n");
return;
}
list->data = newData;
list->capacity = newCapacity;
}
顺序表的使用注意事项
-
初始化:在创建动态顺序表时,需要为其分配初始内存空间。这通常通过
malloc
或calloc
函数完成。分配的内存应该足以存储预期数量的元素,同时留有一定的余地以便于后续的扩展。 -
扩容:当顺序表的元素数量达到当前容量时,需要扩容以避免溢出。扩容通常涉及到申请一块更大的内存空间,并将现有元素复制到新内存区域。扩容策略通常是将容量增加到原来的某个固定比例(如两倍),以减少频繁扩容的性能开销。
-
释放内存:当顺序表不再使用时,应该使用
free
函数释放分配的内存空间,以避免内存泄漏。 -
内存分配失败的处理:在使用
malloc
或realloc
分配内存时,可能会因为各种原因(如内存不足)失败。程序应该检查这些函数的返回值,并在分配失败时进行适当的错误处理。 -
避免内存碎片:频繁的内存分配和释放可能会导致内存碎片,影响程序性能。可以通过适当的内存管理策略(如内存池)来减少内存碎片。
-
数据复制:在扩容时,需要将现有数据复制到新的内存区域。这个过程需要小心处理,确保所有数据都被正确复制,并且顺序不发生变化。
-
内存对齐和访问效率:现代计算机系统通常有内存对齐的要求。在分配内存时,应确保满足这些要求,以提高内存访问效率。
-
引用管理:在涉及到对象引用的动态顺序表中,需要注意管理好引用,确保不再使用的引用能够被垃圾回收,避免内存泄漏。
-
性能监控:在程序运行过程中,应监控内存使用情况,及时发现并解决内存使用中的问题,如内存泄漏、过度扩容等。