数据结构初阶: 顺序表的增删查改

news2025/4/15 16:05:05

顺序表

概念

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

顺序表和数组有什么区别?

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

分类

静态顺序表

概念:使用定长数组存储数据

缺陷:空间给少了不够⽤,给多了造成空间浪费

动态顺序表

动态顺序表顾名思义长度是可以灵活改变的,按需申请空间

动态顺序表的实现

先明确顺序表所需的函数

// 顺序表初始化
void SeqListInit(SeqList* ps);
// 检查顺序表是否满了,满了需要扩容
void SLCheckCapacity(SeqList* ps)
// 顺序表的销毁
void SeqListDestroy(SeqList* ps);
// 顺序表的输出
void SeqListPrint(SeqList* ps);
// 顺序表尾插
void SeqListPushBack(SeqList* ps, SLDateType x);
// 顺序表头插
void SeqListPushFront(SeqList* ps, SLDateType x);
// 顺序表头删
void SeqListPopFront(SeqList* ps);
// 顺序表尾删
void SeqListPopBack(SeqList* ps);
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x);
// 顺序表在任意位置插入数据
void SeqListInsert(SeqList* ps, int pos, SLDateType x);
// 顺序表删除任意位置的值
void SeqListErase(SeqList* ps, int pos);

顺序表的初始化

顺序表需要先进行初始化,如果不初始化,内存内容不确定

  • 顺序表中的元素会包含内存中的随机值(垃圾值)

  • 这些值是不可预测的,可能导致程序行为不一致

还会有安全隐患

  • 未初始化的内存可能包含敏感信息(如果之前存储过)

  • 可能被恶意利用(如信息泄露)

程序错误

  1. 逻辑错误:使用未初始化的值进行计算会导致错误结果

  2. 崩溃风险:如果值被当作指针使用可能导致段错误

  3. 不可重现的bug:在不同环境/时间运行可能表现不同

性能影响

  • 某些情况下,初始化内存可以优化后续访问性能

  • 现代CPU对初始化的内存访问可能有更好的预取行为

// 顺序表数组初始化
void SeqListInit(SeqList* ps)
{
	//ps->arr = NULL;
	//ps->size = ps->capacity = 0;
	ps->arr = (SLDateType*)malloc(sizeof(SLDateType) * 4);
	if (ps->arr == NULL)
	{
		printf("空间开辟失败\n");
		exit(-1);
	}
	ps->size = 0;
	ps->capacity = 4;
}

检查顺序表空间是否需要扩容

当有效数据大于空间时需要对数组进行扩容,扩容通常进行2倍扩容

1. 均摊时间复杂度优化

  • 均摊O(1)插入:2倍扩容可以保证在多次插入操作中,扩容的均摊时间复杂度为O(1)

  • 数学证明:经过n次插入后,总复制次数约为n + n/2 + n/4 + ... ≈ 2n

2. 内存使用效率

  • 空间浪费与复制的平衡:2倍扩容在空间浪费和复制次数之间取得了较好平衡

  • 过小扩容因子(如1.1倍)会导致频繁复制

  • 过大扩容因子(如3倍)会导致过多内存浪费

3. 内存分配器的特性

  • 许多内存分配器对2的幂次大小有优化

  • 2倍扩容减少内存碎片,便于内存池管理

4. 实际实现中的变体

  • 不同语言/库可能有不同选择:

    • Java ArrayList:初始容量10,扩容1.5倍

    • Python list:初始空或根据元素,扩容约1.125倍

    • C++ vector:标准未规定,通常实现为2倍

5. 为什么不是更大的倍数?

  • 内存浪费问题:3倍或更大倍数会导致显著的内存空间闲置

  • 局部性原理:过大的扩容可能使数据分散在不同内存页

数学视角

假设每次扩容因子为k:

  • 扩容次数为O(logₖn)

  • 总复制量为n(1 + 1/k + 1/k² + ...) = n×k/(k-1)

  • k=2时,总复制量≈2n

  • k=1.5时,总复制量≈3n

因此2倍是在复制次数和内存利用率之间的一个合理折中。

void SLCheckCapacity(SeqList* ps)
{
	// 判断数组空间是否满了
	if (ps->size >= ps->capacity)
	{
		ps->capacity *= 2;
		SLDateType* tmp = (SLDateType*)realloc(ps->arr, sizeof(SLDateType) * ps->capacity);
		if (tmp == NULL)
		{
			printf("空间开辟失败\n");
			exit(-1);
		}
		ps->arr = tmp;
	}
}

顺序表的销毁

1. 内存资源释放

  • 防止内存泄漏:动态分配的内存必须显式释放,否则会随着程序运行不断累积

  • 系统资源回收:将不再使用的内存归还给操作系统,供其他程序使用

  • 长期运行程序的关键:对于服务器、后台服务等长期运行程序尤为重要

2. 数据结构生命周期管理

  • 明确所有权:销毁操作明确了谁负责释放资源(创建者负责销毁)

  • 状态重置:通过销毁操作将数据结构标记为"无效状态",防止后续误用

3. 程序健壮性保障

  • 避免悬垂指针:规范的销毁操作会将被释放指针置NULL(如ps->arr = NULL

  • 防御性编程:良好的销毁函数应包含NULL指针检查等安全措施

4. 系统稳定性

  • 防止内存碎片:及时释放不再使用的内存块有助于减少内存碎片

  • 资源竞争预防:在多线程环境中,明确的销毁操作可以避免资源竞争

void SeqListDestroy(SeqList* ps)
{
	assert(ps);  // 检查空指针

	if (ps->arr)    // 先释放内部数组
		free(ps->arr);
	
	ps->arr = NULL;  // 避免悬垂指针
	ps->capacity = ps->size = 0;
}

顺序表的输出

便于观察顺序标中的数据

void SeqListPrint(SeqList* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
}

顺序表的插入

尾插

void SeqListPushBack(SeqList* ps, SLDateType x)
{
	assert(ps);            // 确保ps不为空指针
	SLCheckCapacity(ps);   // 检查空间是否需要扩容
	ps->arr[ps->size] = x; // 将x直接插入最后一个位置
	ps->size++;            // 而后size自增
}

 头插

void SeqListPushFront(SeqList* ps, SLDateType x)
{
	assert(ps);
	SLCheckCapacity(ps);
	for (int i = ps->size; i > 0 ; i--)
	{
        // 将数组从最后一位开始往后移
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;
	++ps->size;
}

 任意位置插入

void SeqListInsert(SeqList* ps, int pos, SLDateType x)
{
	assert(ps);
	assert(pos <= ps->size && pos >= 0);
	SLCheckCapacity(ps);
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	++ps->size;
}

优化头插尾插 

void SeqListPushBack(SeqList* ps, SLDateType x)
{
	SeqListInsert(ps, ps->size, x);
}

void SeqListPushFront(SeqList* ps, SLDateType x)
{
	SeqListInsert(ps, 0, x);
}

顺序表的删除

尾删

void SeqListPopBack(SeqList* ps)
{
	assert(ps && ps->size);
	--ps->size;
}

 头删

void SeqListPopFront(SeqList* ps)
{
	assert(ps && ps->size);
	for (int i = 1; i < ps->size; i++)
	{
		ps->arr[i - 1] = ps->arr[i];
	}
	--ps->size;
}

任意位置删除

void SeqListErase(SeqList* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	int start = pos;
	while (start < ps->size)
	{
		ps->arr[start] = ps->arr[start + 1];
		start++;
	}
	--ps->size;
}

优化尾删头删

void SeqListPopBack(SeqList* ps)
{
	SeqListErase(ps, ps->size);
}

void SeqListPopFront(SeqList* ps)
{
	SeqListErase(ps, 0);
}

顺序表查找

int SeqListFind(SeqList* ps, SLDateType x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			return i; // 找到数据返回下标
		}
	}
	return -1; // 如果没有数据返回-1
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2328020.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

详解AI采集框架Crawl4AI,打造智能网络爬虫

大家好&#xff0c;Crawl4AI作为开源Python库&#xff0c;专门用来简化网页爬取和数据提取的工作。它不仅功能强大、灵活&#xff0c;而且全异步的设计让处理速度更快&#xff0c;稳定性更好。无论是构建AI项目还是提升语言模型的性能&#xff0c;Crawl4AI都能帮您简化工作流程…

【爬虫开发】爬虫开发从0到1全知识教程第14篇:scrapy爬虫框架,介绍【附代码文档】

本教程的知识点为&#xff1a;爬虫概要 爬虫基础 爬虫概述 知识点&#xff1a; 1. 爬虫的概念 requests模块 requests模块 知识点&#xff1a; 1. requests模块介绍 1.1 requests模块的作用&#xff1a; 数据提取概要 数据提取概述 知识点 1. 响应内容的分类 知识点&#xff1a…

SQLark:一款国产免费数据库开发和管理工具

SQLark&#xff08;百灵连接&#xff09;是一款面向信创应用开发者的数据库开发和管理工具&#xff0c;用于快速查询、创建和管理不同类型的数据库系统&#xff0c;目前可以支持达梦数据库、Oracle 以及 MySQL。 对象管理 SQLark 支持丰富的数据库对象管理功能&#xff0c;包括…

防爆对讲机VS非防爆对讲机,如何选择?

在通信设备的广阔市场中&#xff0c;对讲机以其高效、便捷的特点&#xff0c;成为众多行业不可或缺的沟通工具。而面对防爆对讲机与非防爆对讲机&#xff0c;许多用户常常陷入选择困境。究竟该如何抉择&#xff0c;且听我为您细细道来。 防爆对讲机&#xff0c;专为危险作业场…

微信小程序开发:开发实践

微信小程序开发实践研究 摘要 随着移动互联网的迅猛发展&#xff0c;微信小程序作为一种轻量化、无需安装的应用形式&#xff0c;逐渐成为开发者和用户的首选。本文以“个人名片”小程序为例&#xff0c;详细阐述了微信小程序的开发流程&#xff0c;包括需求分析、项目规划、…

操作 Office Excel 文档类库Excelize

Excelize 是 Go 语言编写的一个用来操作 Office Excel 文档类库&#xff0c;基于 ECMA-376 OOXML 技术标准。可以使用它来读取、写入 XLSX 文件&#xff0c;相比较其他的开源类库&#xff0c;Excelize 支持操作带有数据透视表、切片器、图表与图片的 Excel 并支持向 Excel 中插…

青铜与信隼的史诗——TCP与UDP的千年博弈

点击下面图片带您领略全新的嵌入式学习路线 &#x1f525;爆款热榜 88万阅读 1.6万收藏 第一章 契约之匣与自由之羽 熔岩尚未冷却的铸造台上&#xff0c;初代信使长欧诺弥亚将液态秘银倒入双生模具。左侧模具刻着交握的青铜手掌&#xff0c;右侧则是展开的隼翼纹章。当星辰…

「青牛科技」GC5849 12V三相无感正弦波电机驱动芯片

芯片描述&#xff1a; • 4 &#xff5e; 20V 工作电压&#xff0c; 30V 最大耐压 • 驱动峰值电流 2.0A &#xff0c;连续电流 800mA 以内 • 芯片内阻&#xff1a; 900mΩ &#xff08;上桥 下桥&#xff09; • eSOP-8 封装&#xff0c;底部 ePAD 散热&#xff0c;引…

Java基础之反射的基本使用

简介 在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象&#xff0c;都能够调用它的任意属性和方法&#xff1b;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。反射让Java成为了一门动…

大语言模型中的嵌入模型

本教程将拆解什么是嵌入模型、为什么它们在NLP中如此重要,并提供一个简单的Python实战示例。 分词器将原始文本转换为token和ID,而嵌入模型则将这些ID映射为密集向量表示。二者合力为LLMs的语义理解提供动力。图片来源:[https://tzamtzis.gr/2024/coding/tokenization-by-an…

【从零实现Json-Rpc框架】- 项目实现 - 服务端主题实现及整体封装

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

开源的 LLM 应用开发平台Dify的安装和使用

文章目录 前提环境应用安装deocker desktop镜像源配置Dify简介Dify本地docker安装Dify安装ollama插件Dify安装硅基流动插件简单应用练习进阶应用练习数据库图像检索与展示助手echart助手可视化 前提环境 Windows环境 docker desktop魔法环境&#xff1a;访问Dify项目ollama电脑…

从零构建大语言模型全栈开发指南:第五部分:行业应用与前沿探索-5.1.2行业落地挑战:算力成本与数据隐私解决方案

👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 从零构建大语言模型全栈开发指南-第五部分:行业应用与前沿探索5.1.2 行业落地挑战:算力成本与数据隐私解决方案1. 算力成本挑战与优化策略1.1 算力成本的核心问题1.2 算力优化技术方案2. 数据隐私挑战…

NodeJS--NPM介绍使用

1、使用npm install命令安装模块 1.1、本地安装 npm install express 1.2、全局安装 npm install express -g 1.3、本地安装和全局安装的区别

DeepSeek与ChatGPT的优势对比:选择合适的工具来提升工作效率

选DeepSeek还是ChatGPT&#xff1f;这就像问火锅和披萨哪个香&#xff01; "到底该用DeepSeek还是ChatGPT?” 这个问题最近在互联网圈吵翻天!其实这就跟选手机系统-样&#xff0c;安卓党iOS党都能说出一万条理由&#xff0c;但真正重要的是你拿它来干啥&#xff01;&am…

25大唐杯赛道一本科B组知识点大纲(下)

5G/6G网络技术知识点&#xff08;10%&#xff09; 工程概论及通信工程项目实践&#xff08;20%&#xff09; 5G垂直行业应用知识点&#xff08;20%&#xff09; ⭐⭐⭐为重点知识&#xff0c;尽量要过一遍哦 大唐杯赛道一国一备赛思路 大唐杯国一省赛回忆录--有付出就会有收…

Python+Playwright自动化测试-1-环境准备与搭建

1、Playwright 是什么&#xff1f; 微软在 2020 年初开源的新一代自动化测试工具&#xff0c;它的功能类似于 Selenium、Pyppeteer 等&#xff0c;都可以驱动浏览器进行各种自动化操作。它的功能也非常强大&#xff0c;对市面上的主流浏览器都提供了支持&#xff0c;API 功能简…

生产管理系统如何破解汽车零部件行业追溯难痛点

在汽车零部件制造行业中&#xff0c;生产追溯一直是企业面临的核心挑战之一。随着市场竞争的加剧和客户需求的日益复杂&#xff0c;如何确保产品质量、快速定位问题源头、减少批次性返工&#xff0c;成为了每个企业亟待解决的问题。而生产管理系统&#xff0c;作为智能制造的重…

【XTerminal】【树莓派】Linux系统下的函数调用编程

目录 一、XTerminal下的Linux系统调用编程 1.1理解进程和线程的概念并在Linux系统下完成相应操作 (1) 进程 (2)线程 (3) 进程 vs 线程 (4)Linux 下的实践操作 1.2Linux的“虚拟内存管理”和stm32正式物理内存&#xff08;内存映射&#xff09;的区别 (1)Linux虚拟内存管…

umi框架开发移动端h5

1、官网&#xff1a;https://umijs.org/ 2、创建出来的项目 yarn create umi yarn start3、推荐目录结构 . ├── config │ └── config.ts ├── public//静态资源 ├── dist ├── mock │ └── app.ts&#xff5c;tsx ├── src │ ├── .umi │ ├── .um…