数据结构之顺序表(动态)
目录
前言:
二、顺序表的定义与核心特性
2.1 定义
2.2 核心特性
三、顺序表的分类
3.1 静态顺序表
3.2 动态顺序表
四、顺序表的基本操作实现(C 语言)
4.1 结构体定义
4. 2初始化
4.3 扩容操作
4. 4插入元素
4.4.1尾部
4.4.2头部
4.4.3指定
4.5 删除元素
4.5.1尾部
4.5.2头部
4.5.3指定
4.6 查找元素
4.7遍历顺序表
4.8 销毁顺序表
五、顺序表的操作复杂度分析
5.1 时间复杂度
5.2空间复杂度
六、顺序表的优缺点
6.1 优点
6.2 缺点
七、应用场景
八、总结
完
前言:
在数据结构中,线性表是最基础且常用的结构之一,而顺序表作为线性表的重要实现方式,以其存储的连续性和访问的高效性在编程领域占据重要地位。顺序表通过数组的形式存储数据元素,元素之间的逻辑关系通过物理存储位置的连续性来体现,是理解线性表本质和数组应用的关键载体。本文将从原理、实现、操作特性及应用场景等方面,全面解析顺序表的核心知识。
二、顺序表的定义与核心特性
2.1 定义
顺序表是将线性表中的元素按照一定顺序依次存储在一片连续的物理存储空间中的数据结构。简单来说,顺序表就是用数组实现的线性表,数组的下标对应元素的逻辑位置。
2.2 核心特性
- 存储连续性:元素在内存中占据连续的存储空间,相邻元素的物理地址相差一个元素的大小。
- 随机访问:可通过数组下标直接访问任意位置的元素,时间复杂度为 O(1)。
- 固定容量:静态顺序表的容量在初始化时确定,动态顺序表可动态扩容,但扩容会产生额外开销。
- 元素有序性:元素的逻辑顺序与物理存储顺序一致。
三、顺序表的分类
3.1 静态顺序表
- 特点:使用固定大小的数组存储元素,容量一旦确定无法修改。
- 适用场景:元素个数已知且固定的场景,避免动态扩容的开销。
- 缺陷:容量不足时无法添加新元素,容量过大则造成内存浪费。
typedef int SLDataType;
#define N 100
typedef struct SeqList
{SLDataType a[100];//定长数组int size; // 有效数据个数
}SL;
3.2 动态顺序表
- 特点:使用动态分配的数组存储元素,容量可根据需求动态扩容(通常扩容为原容量的 2 倍或 1.5 倍)。
- 适用场景:元素个数不确定、需要灵活增减的场景。
- 优势:内存利用率更高,可灵活应对元素数量的变化。
typedef int SLDataType;
// 动态顺序表 -- 按需申请
typedef struct SeqList
{SLDataType* a;int size; // 有效数据个数int capacity; // 空间容量
}SL;
四、顺序表的基本操作实现(C 语言)
以动态顺序表为例,实现初始化、插入、删除、查找、销毁等核心操作。
4.1 结构体定义
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
// 动态顺序表 -- 按需申请
typedef struct SeqList
{SLDataType* a;int size; // 有效数据个数int capacity; // 空间容量
}SL;
4. 2初始化
void SLInit(SL* ps)
{assert(ps);ps->a = NULL;ps->capacity = ps->size = 0;
}
4.3 扩容操作
void SLCheckCapacity(SL* ps)
{assert(ps);// 扩容函数,容量翻倍//当元数个数等于空间时即为满if (ps->capacity == ps->size){int time = ps->capacity == 0 ? 4 : 2 * ps->capacity;//防止扩容失败找不到原先地址SLDataType* arr = (SLDataType*)realloc(ps->a,sizeof(SLDataType)*time);if (arr == NULL){perror("realloc arr");return;}ps->a = arr;ps->capacity = time;}
}
4. 4插入元素
4.4.1尾部
void SLPushBack(SL* ps, SLDataType x)
{assert(ps);SLCheckCapacity(ps);ps->a[ps->size++] = x;
}
4.4.2头部
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++;
}
4.4.3指定
//指定位置前插入
void SLInsert(SL* ps, int pos, SLDataType x)
{//pos为指定位置assert(ps);assert(pos < ps->size&& pos >= 0);SLCheckCapacity(ps);for (int i = ps->size; i >pos; i--){ps->a[i] = ps->a[i - 1];}ps->a[pos] = x;ps->size++;
}
4.5 删除元素
4.5.1尾部
void SLPopBack(SL* ps)
{assert(ps);assert(ps->size);ps->size--;
}
4.5.2头部
void SLPopFront(SL* ps)
{assert(ps);assert(ps->size);for (int i = 0; i < ps->size - 1; i++){ps->a[i] = ps->a[i+1];}ps->size--;
}
4.5.3指定
//指定位置删
void SLErase(SL* ps, int pos)
{assert(ps);assert(ps->size);assert(pos < ps->size&& pos >= 0);for (int i = pos; i < ps->size-1; i++){ps->a[i] = ps->a[i + 1];}ps->size--;
}
4.6 查找元素
//返回下标,没有就返回-1
int SLFind(SL* ps, SLDataType x)
{assert(ps);for (int i = 0; i < ps->size; i++){if (ps->a[i] == x){return i;}}return -1;
}
4.7遍历顺序表
void SLPrint(SL* ps)
{assert(ps);for (int i = 0; i < ps->size; i++){printf("%d ", ps->a[i]);}printf("\n");
}
4.8 销毁顺序表
void SLDestroy(SL* ps)
{assert(ps);if(ps->a){free(ps->a);ps->a = NULL;}ps->capacity = ps->size = 0;
}
五、顺序表的操作复杂度分析
5.1 时间复杂度
- 访问元素:O(1),直接通过下标访问。
- 插入/删除元素:O(n),需移动后续元素(最坏情况下移动 n 个元素)。
- 查找元素:O(n),需遍历数组(未排序)。
- 扩容操作:O(n),需拷贝原数组元素到新数组。
5.2空间复杂度
- 整体空间复杂度为 O(n),n 为顺序表的容量。
六、顺序表的优缺点
6.1 优点
- 随机访问效率高,适合频繁查询的场景。
- 存储密度高,无需额外空间存储元素间的逻辑关系。
- 实现简单,易于理解和操作。
6.2 缺点
- 插入/删除操作效率低,元素移动开销大。
- 动态扩容存在内存浪费和拷贝开销。
- 固定容量的静态顺序表灵活性差。
七、应用场景
- 频繁查询、少量增删的场景,如成绩排名表、静态数据缓存。
- 需要随机访问元素的场景,如数组模拟栈、队列。
- 内存空间充足,追求访问效率优先的场景。
八、总结
顺序表作为线性表的基础实现,其核心优势在于随机访问的高效性,而短板则是插入删除操作的低效性。在实际开发中,需根据业务场景选择合适的线性表实现:若频繁查询,顺序表是优选;若频繁增删,则链表更合适。理解顺序表的原理和操作特性,不仅能帮助我们高效解决问题,更能为后续学习栈、队列等复杂数据结构奠定坚实基础。
