目录
1. 什么是顺序表?
1.1 顺序表的定义
顺序表(Sequence List) 是一种使用连续内存空间存储数据的线性数据结构,本质上就是动态数组。它具备以下特点:
- 顺序存储:元素在内存中是连续存放的,可随机访问(即
O(1)
时间复杂度访问)。 - 动态扩容:当存储空间不足时,可以自动扩容,克服静态数组的限制。
- 支持插入、删除、查找:通过索引可以直接访问元素,也可以对数据进行动态操作。
1.2 顺序表 vs. 链表
数据结构 | 存储方式 | 访问速度 | 插入/删除速度 | 额外空间 |
---|---|---|---|---|
顺序表 | 连续内存 | 快 O(1) | 慢 O(n) | 需要预留空间 |
链表 | 非连续内存 | 慢 O(n) | 快 O(1) (头插法) | 需要额外指针存储 |
- 顺序表适用于:查找频繁的场景(因为支持随机访问)。
- 链表适用于:插入、删除频繁的场景(因为不需要移动数据)。
顺序表的基本操作
操作 | 说明 |
---|---|
初始化(Init) | 创建一个空的顺序表 |
插入(PushBack, PushFront, Insert) | 在表尾、表头或指定位置插入元素 |
删除(PopBack, PopFront, Erase) | 删除表尾、表头或指定位置的元素 |
查找(Find) | 查询某个元素是否存在,并返回索引 |
打印(Print) | 输出顺序表中的所有元素 |
销毁(Destroy) | 释放动态分配的内存,防止内存泄漏 |
2. 结构设计
在 SeqList.h
头文件中,我们定义顺序表的数据结构和相关操作:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// 定义数据类型(可以换成 float、double 等)
typedef int SLDataType;
// 顺序表结构体
typedef struct SequenceList {
SLDataType* arr; // 动态数组指针
int size; // 当前元素个数
int capacity; // 当前数组的容量
} SL;
// 函数声明
void SLInit(SL* ps); // 初始化
void SLDestroy(SL* ps); // 销毁顺序表
void SLPrint(SL* ps); // 打印顺序表
void SLPushBack(SL* ps, SLDataType x); // 末尾插入
void SLPopBack(SL* ps); // 末尾删除
void SLPushFront(SL* ps, SLDataType x); // 头部插入
void SLPopFront(SL* ps); // 头部删除
void SLInsert(SL* ps, int pos, SLDataType x); // 指定位置插入
void SLErase(SL* ps, int pos); // 指定位置删除
int SLFind(SL* ps, SLDataType x); // 查找元素
数据结构解释
arr
:指向动态分配的数组,存储顺序表中的元素。size
:当前已存储的元素个数(用于控制数据访问范围)。capacity
:当前数组的总容量(当size == capacity
时,需要扩容)。
3. 代码实现
3.1 初始化
void SLInit(SL* ps)
{
ps->arr = NULL; // 初始时不分配空间
ps->size = 0;
ps->capacity = 0;
}
解释:
arr
设为 NULL,表示还未分配内存。size
设为 0,因为表里没有元素。capacity
设为 0,表示没有预分配空间。
3.2 扩容机制
当 size == capacity
时,必须扩展内存,否则无法继续插入新元素。
void SLCheckCapacity(SL* ps)
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity; // 扩容策略
if (ps->size == ps->capacity)
{
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("内存分配失败!");
return;
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
扩容策略:
- 初次分配:如果
capacity == 0
,分配4
个单位的空间。 - 后续扩容:当空间不足时,容量扩大 2 倍,提高效率(减少
realloc
调用次数)。
3.3 插入元素
尾部插入
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps != NULL);
SLCheckCapacity(ps);
ps->arr[ps->size++] = x;
}
步骤:
- 先检查是否需要扩容。
- 在
arr[size]
位置插入数据x
。 size++
,更新当前元素数量
头部插入
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps != NULL);
SLCheckCapacity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
++ps->size;
}
步骤:
- 先检查是否需要扩容。
- 所有元素后移,腾出
arr[0]
位置。 - 插入
x
,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->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
++ps->size;
}
- 检查
pos
是否有效:pos
不能超过size
。 - 扩容:确保有足够空间。
- 元素后移,插入
x
。
3.4 删除元素
尾部删除
void SLPopBack(SL* ps)
{
assert(ps && ps->size);
--ps->size;
}
步骤:
- 检查
size > 0
,防止越界访问。 - 直接
size--
,逻辑上删除元素。
头部删除
void SLPopFront(SL* ps)
{
assert(ps && ps->size);
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
--ps->size;
}
- 所有元素前移,覆盖
arr[0]
。 size--
,更新元素个数。
任意位置删除
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
--ps->size;
}
- 检查
pos
是否有效。 - 元素前移,覆盖
pos
位置。
3.5 打印顺序表
void SLPrint(const SL* ps)
{
assert(ps != NULL); // 确保指针有效
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
✅ 作用:
- 遍历
arr
,按顺序打印所有元素。
3.6 查找元素
int SLFind(const SL* ps, SLDataType x)
{
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
✅ 逻辑:
- 遍历
arr
,找到x
并返回索引,否则返回-1
。
3.7 释放内存
void SLDestroy(SL* ps)
{
free(ps->arr);
ps->arr = NULL;
ps->capacity = 0;
ps->size = 0;
}
✅ 作用:
- 释放
arr
,避免内存泄漏。
4. 测试
#include "SeqList.h"
void test()
{
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPushBack(&sl, 5);
SLPushFront(&sl, 0);
SLPrint(&sl);
SLPopBack(&sl);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
SLInsert(&sl, 3, 250);
SLPrint(&sl);
SLErase(&sl, 3);
SLPrint(&sl);
SLDestroy(&sl);
}
int main()
{
test();
return 0;
}