数据结构入门系列——顺序表详解:概念、分类与动态实现

「 每日一句 · Daily Quote 」

"我这个人走得很慢,但是我从不后退。"

--- 亚伯拉罕·林肯

文章目录

  • 前言
  • 一、线性表
  • 二、顺序表
    • [2.1 概念与结构](#2.1 概念与结构)
    • [2.2 分类](#2.2 分类)
      • [2.2.1 静态顺序表](#2.2.1 静态顺序表)
      • [2.2.2 动态顺序表](#2.2.2 动态顺序表)
  • 三、动态顺序表的实现
  • [3.1 定义动态顺序表的结构](#3.1 定义动态顺序表的结构)
    • [3.2 初始化顺序表](#3.2 初始化顺序表)
    • [3.3 检查空间容量是否足够](#3.3 检查空间容量是否足够)
    • [3.4 打印顺序表](#3.4 打印顺序表)
    • [3.5 销毁顺序表](#3.5 销毁顺序表)
    • [3.6 尾插](#3.6 尾插)
    • [3.7 头插](#3.7 头插)
    • [3.8 尾删](#3.8 尾删)
    • [3.9 头删](#3.9 头删)
    • [3.10 在指定位置之前插入数据](#3.10 在指定位置之前插入数据)
    • [3.11 删除pos位置的数据](#3.11 删除pos位置的数据)
    • [3.12 查找](#3.12 查找)
  • 总结

前言

数据结构是程序员的必修内功,而顺序表是最基础、最常用的线性结构之一。本文将从线性表的概念入手,介绍顺序表的定义及其与数组的区别,对比静态与动态顺序表的优劣,重点讲解动态顺序表的完整实现,包括初始化、扩容、头尾插入删除、指定位置插入删除、查找等核心接口,并逐段分析代码逻辑。掌握顺序表,是学习链表、栈、队列等复杂数据结构的第一步。


一、线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是⼀种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不⼀定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。


二、顺序表

2.1 概念与结构

概念:顺序表是用⼀段物理地址连续 的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。

顺序表和数组的区别?

顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口

打个比方

数组相当于未经雕琢的"基础食材/菜品",仅提供最底层的连续物理存储能力;

而顺序表则是以此为核心原料进行高级封装后的"完整料理",其底层结构依然是原生数组,却拥有增、删、改、查等一系列标准数据结构接口

2.2 分类

2.2.1 静态顺序表

概念:使用定长数组存储元素

静态顺序表缺陷:空间给少了不够用,给多了造成空间浪费

2.2.2 动态顺序表


三、动态顺序表的实现

3.1 定义动态顺序表的结构

c 复制代码
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* arr;
	int size;      //有效数据个数
	int capacity;  //空间容量
}SL;
  • typedef int SLDataType
    • 对类型重命名: 方便未来需要储存其他新的类型变量
  • typedef struct SeqList { ... } SL;
    • 结构体定义与别名: 提升书写效率
  • SLDataType* arr
    • 首元素地址
  • size
    • 有效数据个数 : 记录当前顺序表中实际已存入的元素数量
  • capacity
    • 物理容量上限: 指该顺序表最大能储存的元素个数

3.2 初始化顺序表

c 复制代码
	//初始化
	void SLInit(SL* ps)
	{

		ps->arr = NULL;
		ps->size = ps->capacity = 0;
	}

arr置为空,size,capacity置为0

3.3 检查空间容量是否足够

c 复制代码
void Checkcapacity(SL* ps)
{
	assert(ps);
	if (ps->size == ps->capacity)
	{
		//检查空间容量是否为0
		int newcapcity = (ps->capacity == 0) ? 4 : ps->capacity * 2;
		//扩容
		//realloc第二个参数,单位是字节
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newcapcity * sizeof(SLDataType));
		//扩容失败
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		//扩容成功
		ps->arr = tmp;
		ps->capacity = newcapcity;
	}
}
    1. 如果size == capacity,说明容量已经满了,需要扩容
    1. 检查空间容量是否为0,如果空间容量为0,则默认赋4个元素空间;如果空间容量不为0, 则按 2 倍增长
    1. 用realloc进行扩容,并用临时变量tmp接收
    1. 判断扩容是否成功,若扩容成功,则更新结构体元数据

3.4 打印顺序表

c 复制代码
//打印
void SLPrint(SL* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
}
    1. 用for循环遍历所有元素并打印
    1. 换行

3.5 销毁顺序表

c 复制代码
//销毁
void SLDestroy(SL* ps)
{
	assert(ps);
	ps->capacity = ps->size = 0;
	free(ps->arr);
	ps->arr = NULL;
}
    1. 令capacity和size都为0
    1. free掉之前通过realloc申请的内存
    1. 将arr指针置空,规避野指针

3.6 尾插

c 复制代码
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);
	Checkcapacity(ps);
	ps->arr[ps->size++] = x;
}
    1. checkcapacity检查一下空间容量是否足够
    1. 有效元素自增1,并且将数据写入

3.7 头插

c 复制代码
//头插
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	Checkcapacity(ps);
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i-1];
	}
	ps->arr[0] = x;
	//增加size数量
	ps->size++;
}
    1. 先用Checkcapcity检查空间容量是否足够
    1. 利用for循环将每个元素向后移动1位
    1. 将首位元素写入并将有效元素个数加1

3.8 尾删

c 复制代码
//尾删
void SLPopBack(SL* ps)
{
	//检查ps和size都不为空
	assert(ps && ps->size);
	Checkcapacity(ps);
	ps->size--;
}
    1. 确保pssize都不为空
    1. 将有效元素个数减1

3.9 头删

c 复制代码
//头删
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--;
}
    1. 先用Checkcapacity检查空间容量是否充足
    1. 将每个元素复制到前一个元素位置
    1. 将有效元素个数减1

3.10 在指定位置之前插入数据

c 复制代码
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps && pos >= 0 && pos <= ps->size);
	//检查空间是否足够
	Checkcapacity(ps);

	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}
    1. 检查空间容量是否足够
    1. 将下标区间[pos, size - 1]内的元素整体向后移动一位。
    1. 将目标值 x 写入pos位置
    1. 有效元素个数加1

3.11 删除pos位置的数据

c 复制代码
//删除pos位置的数据
void SLErase(SL* ps, int pos)
{
	assert(ps && pos >= 0 && pos <	 ps->size);
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}
    1. 将下标区间[pos, size - 1]内的元素整体向前移动一位。
    1. 有效元素个数减1

3.12 查找

c 复制代码
//查找
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		//找到了
		if (ps->arr[i] == x)
			return i;
	}

	//没找到
	return -1;
}
    1. 将所有元素遍历一遍,寻找目标元素
    1. 找到目标元素,返回该元素下标
    1. 没找到该元素,返回-1

总结

以上就是本篇博客的核心内容。本文介绍了线性表的概念与分类,重点讲解了顺序表的定义及其两种实现方式(静态与动态顺序表),详细实现了动态顺序表的常用接口:初始化、扩容、头尾插入删除、指定位置插入删除、查找、打印与销毁,并分析了各接口的代码逻辑与注意事项。掌握顺序表,就为后续学习链表、栈、队列等数据结构打下了坚实基础。

相关推荐
smj2302_7968265244 分钟前
解决leetcode第4033题有效K个不同元素子数组I
数据结构·python·算法·leetcode
witton1 小时前
xmake中将proto文件生成C/C++文件然后再编译链接进项目
c语言·c++·mingw·protobuf·xmake·protobuf-c·protoc-gen-c
狂师1 小时前
AI 测试丨一句指令生成测试报告,带失败截图、操作录屏、Trace、日志,这套 Skill 思路可以直接抄...
人工智能·开源·测试
dear_bi_MyOnly1 小时前
C语言控制语句与循环逻辑精讲
c语言·开发语言·学习·分类
sibylyue1 小时前
# Web端流媒体JS播放器开源库
前端·javascript·开源
存在morning1 小时前
【PySpark 学习笔记 六】性能优化:分区、Shuffle、缓存与数据倾斜
笔记·学习·性能优化
大鹅办公1 小时前
苹果手机忘记锁屏密码怎么办?2026年5种方法实测(含保数据方案)
经验分享·智能手机·苹果手机
chunmiao30321 小时前
达摩院肝癌AI模型登上《自然·医学》:可识别1厘米微小肿瘤
开源
tianxuanjg1 小时前
工业机器人精密零件采购:如何选关节 CNC 加工供应商
经验分享·机器人·无人机·制造·材质