【第一章】数据结构------预备知识
本章我们就正式进入了数据结构的部分,那么在本章中将会介绍数据结构的来源,还有相关的概念术语,以及对于时空复杂度的详解。
为什么我们需要数据结构
数据结构是计算机科学中组织和存储数据的方式,直接影响程序的效率、可读性和可维护性。合理选择数据结构可以优化资源使用,提升性能。不同的数据结构在操作(如插入、删除、搜索)上有不同的时间复杂度。例如,哈希表可以实现平均O(1)时间复杂度的查找,而数组则需要O(n)。选择合适的数据结构能显著减少计算资源消耗。数据结构的设计影响内存占用,链表适合频繁插入删除的场景,而数组更适合随机访问。压缩数据结构如位图或布隆过滤器可以在特定场景下大幅节省内存。许多复杂问题可以通过适当的数据结构抽象化。图结构能建模网络关系,树结构能表示层次数据,堆能高效处理优先级队列。数据结构提供了一种问题分解和建模的工具。良好设计的数据结构使代码更易理解和维护。封装数据操作接口,隐藏实现细节,可以减少代码耦合度。标准数据结构如栈、队列等提供了通用的设计模式。大多数算法依赖于特定的数据结构。排序算法需要可随机访问的集合,最短路径算法需要图结构。数据结构与算法紧密相关,共同决定了程序的整体性能。数据结构能有效表示现实世界中的关系和模式。数据库索引使用B树,社交网络使用图结构,文件系统使用树状结构。选择合适的数据结构可以更自然地映射实际问题。
当然,数据结构本身可以供我们使用来更好的管理数据,同样的在算法中我们也会大量使用到数据结构。
常用的数据结构
这里我尽量为大家列举,不仅包括我们一般计算机学生上课所接触的,还有一些竞赛中经常使用的数据结构。
- 线性结构:顺序表,单链表,双向链表,栈和队列(操作受限的线性表),串,数组,广义表,哈希表(散列表)
- 非线性结构:树(二叉树,二叉搜索树,二叉平衡树,AVL/红黑树,堆(本质是完全二叉树),Trie,B+/B-树),图
- 特殊的数据结构:并查集(森林),跳表,布隆过滤器,线段树,树状数组
数据结构相关概念术语(易混淆)
下面是一些教材上常见的一些概念术语,可能在开发环境中不会经常使用了,但是应付一般的考试足够了。
- 数据:数据是信息的载体
- 数据元素:是数据的基本单位
- 数据项:是组成数据元素的、拥有独立含义的、不可分割的最小单位
- 数据对象:是性质相同的数据元素的集合,是数据的一个子集。
数据结构的存储形式
逻辑结构
- 集合结构
- 线性结构
- 树结构
- 图结构和网状结构
存储结构
- 顺序存储结构
- 链式存储结构
抽象数据类型(ADT)
抽象数据类型是一种数学模型,用于描述数据的逻辑结构和操作,而不涉及具体实现细节。它通过封装数据和对数据的操作,隐藏内部实现,仅暴露接口给用户。
ADT的核心特征
数据抽象 将数据的具体表示与使用分离,用户只需知道能执行哪些操作,无需了解数据如何存储或操作如何实现。
封装性 ADT将数据和操作绑定在一起,形成一个独立的模块。外部只能通过定义的接口访问数据,无法直接操作内部数据。
ADT的组成要素
数据对象 描述ADT中数据的逻辑结构,例如栈中的元素遵循后进先出(LIFO)原则。
操作集合 定义对数据对象的所有合法操作。以栈为例,基本操作包括:
- push(item):将元素压入栈顶
- pop():移除并返回栈顶元素
- peek():查看栈顶元素但不移除
- isEmpty():检查栈是否为空
ADT的实现方式
物理实现 ADT可通过不同数据结构实现。例如栈可以用数组或链表实现:
数组实现:需要预先分配固定大小空间
链表实现:动态分配内存,更灵活
接口与实现分离 在任何编程语言中,ADT都强调接口定义与具体实现的分离。例如C语言中用头文件声明接口,源文件实现具体功能。
ADT的典型示例
队列ADT
数据对象:先进先出(FIFO)的元素序列
操作集合:
enqueue(item):在队尾添加元素
dequeue():移除队首元素
front():获取队首元素
isEmpty():检查队列是否为空
二叉树ADT
数据对象:具有根节点和左右子树的层次结构
操作集合:
insert(key):插入新节点
search(key):查找特定节点
traverse(mode):按指定顺序遍历(前序、中序、后序)
ADT的设计原则
信息隐藏 只暴露必要的操作接口,隐藏数据存储和算法细节。例如栈不暴露当前容量或扩容策略。
一致性保证 操作应维护数据的不变式。例如执行pop操作前必须确保栈非空,否则应抛出异常或返回错误代码。
算法和算法分析
算法是解决特定问题的一系列明确指令,具有以下特性:
有穷性:必须在有限步骤内终止。
确定性:每条指令无歧义。
可行性:可通过基本操作实现。
输入/输出:有零个或多个输入,至少一个输出。
算法效率的衡量标准
时间复杂度描述算法运行时间随输入规模的增长趋势,常用大O符号表示:
时间复杂度
时间复杂度用于衡量算法执行时间随输入规模增长的变化趋势。它不计算实际运行时间,而是分析操作次数的增长级数,通常用大O符号(O)表示。
常见时间复杂度类型
-
O(1) 常数时间
算法的执行时间不随输入规模变化。例如访问数组元素或哈希表查找。
-
O(log n) 对数时间
执行时间与输入规模的对数成正比。常见于二分查找或平衡二叉树的搜索操作。
-
O(n) 线性时间
执行时间与输入规模呈线性关系。例如遍历数组或链表。
-
O(n log n) 线性对数时间
常见于高效排序算法如快速排序、归并排序。
-
O(n²) 平方时间
通常出现在嵌套循环中,如冒泡排序或选择排序。
-
O(2ⁿ) 指数时间
多见于递归解决子问题重叠的情况,如暴力破解算法。
时间复杂度的计算方法
忽略低阶项
对于表达式如 3n² + 2n + 1,仅保留最高阶项 n²,记为 O(n²)。
忽略常数系数
若操作次数为 5n,时间复杂度仍记为 O(n)。
例外:循环结构的分析
单层循环:若循环次数与 n 成正比,记为 O(n)。
嵌套循环:若每层循环次数为 n,则 k 层嵌套为 O(nᵏ)。
递归算法的分析
使用递归树或主定理(Master Theorem)计算。例如斐波那契数列的递归实现为 O(2ⁿ)。
时间复杂度的实际意义
算法选择依据:处理大规模数据时,优先选择 O(n log n) 而非 O(n²) 的算法。
性能优化方向:通过降低时间复杂度优化关键代码段。
资源预估:帮助预估算法在数据增长时的可扩展性。
⚠️注意事项:
最坏与平均情况:某些算法(如快速排序)需区分最坏和平均时间复杂度。
实际因素影响:缓存、硬件差异等可能导致实际表现与理论分析不符。
空间换时间:哈希表等数据结构通过增加空间复杂度来降低时间复杂度。
通常来说。对于时间复杂度的计算,我们经常会按照算法的执行的次数来评估,根据数学知识可以知道,在一个表达式当中影响最大的就是未知数的最高阶数次。那么这就是时间复杂度的计算方式。
好的,那么数据结构部分的前置知识就介绍到这里。本章介绍了数据结构的相关术语,存储形式以及算法的分析。下一章我们开始进行线性表的讲解,这不仅是数据结构最为基础的部分,也是后面更复杂数据结构的基础。那么下一章我们不见不散~