1. 什么是顺序表?
顺序表是一个使用连续内存块来存储元素的线性表。其主要特点是支持通过索引(下标)快速访问任何位置的元素,因此,它的时间复杂度为O(1)。顺序表通常用于需要快速访问和修改元素的场景。
顺序表通常支持以下操作:
- 插入:在表的头部、尾部或中间插入元素。
- 删除:从表的头部、尾部或中间删除元素。
- 查找:通过元素值查找元素的下标。
- 打印:打印顺序表中的所有元素。
顺序表的大小是固定的,一旦创建,初始容量就决定了它最多能容纳多少元素。如果表满了,插入新元素时需要扩容。
2. 顺序表的结构
顺序表一般包括以下几个成员变量:
- 数组(a):存储元素的数组。
- 大小(size):顺序表中当前元素的数量。
- 容量(capacity):顺序表当前能够存储的最大元素个数。
以下是一个顺序表的结构体定义示例:
typedef int SLDateType;
typedef struct SeqList{
SLDateType *a; // 存储数据的数组
int size; // 当前元素个数
int capacity; // 数组的容量
} SeqList;
3. 顺序表的基本操作实现
在实际应用中,顺序表需要进行许多常见的操作,包括插入、删除、查找和扩容等。下面我们将逐一讨论这些操作的实现方式。
3.1 初始化顺序表
在顺序表初始化时,我们通常将数组指针设为NULL,并将大小和容量都初始化为0。
void SeqListInit(SeqList *ps){
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
3.2 销毁顺序表
销毁顺序表时,我们需要释放已分配的内存空间,并将相关成员变量重置为初始状态。
void SeqListDestroy(SeqList *ps){
free(ps->a);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
3.3 打印顺序表
打印顺序表是一个常见操作,通常用于调试和展示数据。我们可以通过循环遍历顺序表中的每个元素来实现。
void SeqListPrint(SeqList *ps){
for (int i = 0; i < ps->size; i++){
printf("%d ", ps->a[i]);
}
printf("\n");
}
3.4 插入元素
插入元素时,我们首先检查顺序表是否已经满了。如果满了,就需要扩容。然后将元素插入指定位置,并将后续元素后移。
- 尾部插入:
void SeqListPushBack(SeqList *ps, SLDateType x){ // 检查是否需要扩容
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDateType *tmp = (SLDateType *)realloc(ps->a, newCapacity * sizeof(SLDateType));
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
ps->a[ps->size] = x;
ps->size++;
}
- 头部插入:
void SeqListPushFront(SeqList *ps, SLDateType x){
// 检查是否需要扩容
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDateType *tmp = (SLDateType *)realloc(ps->a, newCapacity * sizeof(SLDateType));
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
} // 将现有元素后移
for (int i = ps->size; i > 0; i--)
{
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->size++;
}
3.5 删除元素
删除元素时,顺序表需要将元素删除后,后面的元素前移。具体操作分为尾部删除和头部删除两种情况。
- 尾部删除:
void SeqListPopBack(SeqList *ps)
{
if (ps->size == 0)
{
printf("SeqList is empty\n");
return;
}
ps->size--;
}
- 头部删除:
void SeqListPopFront(SeqList *ps)
{
if (ps->size == 0)
{
printf("SeqList is empty\n");
return;
} // 将元素前移
for (int i = 0; i < ps->size - 1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
3.6 查找元素
顺序表支持按值查找元素,并返回其索引。如果找不到该元素,返回-1。
int SeqListFind(SeqList *ps, SLDateType x)
{
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
3.7 插入指定位置
顺序表支持在指定位置插入元素。与尾部插入类似,但需要将指定位置后面的元素后移。
void SeqListInsert(SeqList *ps, int pos, SLDateType x)
{
assert(pos >= 0 && pos <= ps->size); // 检查是否需要扩容
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDateType *tmp = (SLDateType *)realloc(ps->a, newCapacity * sizeof(SLDateType));
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
} // 将元素后移
for (int i = ps->size; i > pos; i--)
{
ps->a[i] = ps->a[i - 1];
}
ps->a[pos] = x;
ps->size++;
}
3.8 删除指定位置的元素
与删除头部或尾部元素类似,删除指定位置的元素需要将后面的元素前移。
void SeqListErase(SeqList *ps, int pos)
{
assert(pos >= 0 && pos < ps->size); // 将元素前移
for (int i = pos; i < ps->size - 1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
4. 顺序表的优缺点
优点:
- 支持随机访问,时间复杂度为O(1)。
- 内存布局连续,便于缓存优化,效率较高。
缺点:
- 插入和删除操作可能需要移动大量元素,时间复杂度为O(n)。
- 需要预留足够的空间,避免频繁扩容,内存浪费可能较大。
5. 总结
顺序表是一种基础而常用的数据结构,适用于需要频繁访问和修改数据的场景。它的实现简单且高效,但对于频繁插入删除操作的场景可能并不理想。理解顺序表的基本操作有助于更好地选择和使用合适的数据结构。