该部分为学习笔记,具体内容详见:《数据结构(Python语言描述)》一书
第2章 数据结构概述
第一节 基本概念
一、数据与数据结构
- 数据:数据是指所有能够输入计算机中存储并被计算机处理的符号的总称。
- 数据元素:是数据集合中的一个“个体”,是数据的基本单位,在计算机程序中通常将其作为一个逻辑整体进行处理。
- 数据项:当一个数据元素包含多个部分信息时,其中每个部分的信息被称为数据项。也被称为字段、域或属性。
- 数据对象:是性质相同的数据元素的集合,是数据的一个子集。
- 数据结构:带关系的数据元素构成的集合称为数据结构。
- 逻辑结构:是指数据结构中各数据元素之间的内在关系。包括:线性结构、树形结构、图状结构和集合。
- 线性结构:普通的线性表、栈和队列等
- 树形结构:元素之间存在“一对多”的关系,层次型的结构
- 图状结构:元素之间存在“多对多”的关系
- 集合:数据元素之间无其他关系
- 注:树形结构、图状结构和集合都不属于线性结构,统称为非线性结构。
- 注:逻辑结构除了可以用逻辑结构示意图表示之外,还可以用二元组方式来表示。
- 存储结构:是指数据结构在计算机中的表示(映像)方法,是逻辑结构在计算机中的存储实现。对数据结构的存储应包含数据元素本身及数据元素之间的关系。包括:顺序存储、链式存储、索引顺序存储、哈希存储。
- 顺序存储:所有的数据元素存放在一篇连续的存储单元中。
- 链式存储:每个数据元素在内存中的映像包含元素值和指针域两个部分,被称为结点。每个结点单独分配空间,因此无需占用连续内存。
- 索引顺序存储:包含两个部分,即主数据表和索引表,其主要用于快速查找数据元素。
- 哈希存储:也称为哈希表和散列表,通常是一个稀疏顺序表,用于存储集合,目的是提高数据的查找效率
二、数据类型
- 数据类型及分类
按照对象值是否可分解,数据类型也常被分为原子类型和组合类型。 - 抽象数据类型
是指一个数学模型及定义在该模型上的一组操作,简称ADT。
第二节 数据结构课程讨论的内容
程序设计的实质在于解决两个主要问题:一是根据实际问题选择一种好的数据结构;二是设计一个好的算法,而好的算法在很大程度上取决于描述实际问题的数据结构。
数据结构是一门研究现实世界中数据元素的逻辑关系、基本操作以及在计算机中如何存储表示和实现的学科。它以高级语言程序设计为基础,需要一些数学知识作为前导,它也是算法分析、操作系统、软件工程等课程的必修先行课,是一门非常关键的课程。
第三节 算法及性能分析
一、算法
- 算法的定义
- 算法是对特定问题的求解步骤的一种描述,它是指令的有限序列,其中每条指令表示一个或多个操作。
- 算法的性质
- 有穷性:算法必须在执行又穷步骤后结束,而且其中的每个步骤都是有穷的。
- 可行性:算法的每条指令所对应的操作可以完全机械地进行,并且可在有限的时间、空间资源下完成。
- 确定性:算法中的每条指令应确切定义,没有歧义,即对于某个确定的输入,算法的执行路径和执行结果都是唯一的。
- 输入:算法有明确定义的0个或多个输入。
- 输出:算法有明确定义的1个或多个输出,表示算法的处理结果。
- 算法与程序
- 算法描述
二、算法分析基础
- 算法设计目标
- 正确性:要求算法能够正确地执行,对于一切合法的输入数据都能够得出满足要求的结果。
- 可读性:算法应该容易阅读,提高算法可读性的方法有两个:一是给算法添加注释,以方便程序设计者和后续维护人员阅读和差错;二是要注意对函数、变量等对象进行合理命名。
- 健壮性:算法应具有容错处理的功能,当输入的数据不合法或运行环境改变时,算法都能恰当的做出反应或数据处理。可以通过在算法中增加异常处理语句,对算法进行不断测试与优化,达到算法的健壮性。
- 高效率:算法的效率分为时间效率和空间效率(执行过程中占用的内存空间少则空间效率高)。
- 算法分析
由于在算法效率中更关注时间效率,算法时间效率的分析方法主要有两种方法:时间性能的事后实验统计与时间性能的事前分析估算。 - 时间复杂度及计算方法
将运行时间是常量时间的操作称为“原操作”,将算法中的所有语句执行的原操作的次数之和 T(n) 作为时间代价量度, T(n) 称为时间函数或时间代价函数。如:数值类型数据的赋值、所有数学运算、原子类型的数据的输入和输出等都是原操作,而判断 x 是否在长度为 n 的列表中的 in 操作不是原操作,它包含了若干次数的值比较操作。具体的时间复杂度计算方法如下:
- 将所有语句中原操作的执行次数之和作为算法的运行时间函数 T(n)。
- 忽略时间函数 T(n) 的低次项部分和最高次项的系数,只取时间函数的最高次项,并辅以大 O 记号表示。
- 时间复杂度计算的简化方法
关键操作通常是循环最深层的一个语句,它是算法中执行原操作的次数数量级最高的一个语句。 - Python 内置类型基本操作的时间性能
- 注:list.insert 与 list.append() 的时间复杂度的区别
- 列表常见操作的时间复杂度
基 本 操 作 | 说 明 | 平均时间复杂度 |
---|---|---|
copy | 复制 | O(n) |
append | 在尾部插入 | O(1) |
pop list | 删除最后一个元素 | O(1) |
pop intermediate | 删除非尾部位置的元素 | O(k) |
insert | 插入 | O(n) |
get item | 元素值的读取 | O(1) |
set item | 元素值的写入 | O(1) |
delete item | 删除值为 item 元素 | O(n) |
set slice | 设置切片(切片长度为 k) | O(n + k) |
get slice | 切片 k 个元素 | O(n) |
sort | 排序 | O(nlog2n) |
multiply | 乘 k | O(nk) |
x in s | 判断 x 是否在表 s 中 | O(n) |
min(s)、max(s) | 求表 s 的最小值、最大值 | O(n) |
get length | 求表长 | O(1) |
- 字典常见操作的时间复杂度
基 本 操 作 | 说 明 | 平均时间复杂度 |
---|---|---|
copy | 复制 | O(n) |
get item | 读取元素 | O(1) |
set item | 写入元素 | O(1) |
delete item | 删除元素 | O(1) |
iteration | 遍历 | O(n) |
- 常见的时间复杂度及性能排序
- 常量阶: O ( 1 ) O(1) O(1)
- 对数阶: O ( l o g m n ) O(log_m n) O(logmn),其中 m 为大于 1 的正常数,例如 O ( l o g 2 n ) O(log_2 n) O(log2n)
- 线性阶: O ( n ) O(n) O(n)
- 线性对数阶: O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n)
- 多项式阶: O ( n m ) O(n^m) O(nm),其中 m 为常数,例如 O ( n 2 ) 、 O ( n 3 ) O(n^2)、O(n^3) O(n2)、O(n3)
- 指数阶: O ( m n ) O(m^n) O(mn),其中 m 为常数,例如 O ( 2 n ) 、 O ( 3 n ) 、 O ( n ! ) O(2^n)、O(3^n)、O(n!) O(2n)、O(3n)、O(n!)
- 时间复杂度计算原则
- 加法原则
- 乘法原则
- 其他情况
- 时间复杂度与输入实例和参数等相关
- 摊销时间复杂度
有点算法会在触发某个条件时做额外的工作,此时可以根据这个条件发生的概率对整个算法进行性能评估,这样得到的时间复杂度称为摊销时间复杂度。 - 多个问题规模的情况
- 空间复杂度
三、同一问题的不同算法
现在设计 4 个不同的算法,判断列表中是否含有重复元素。例如【1,2,1,3】中含有重复元素,【1,2,3】中不含重复元素。
- 搜索法
- 排序法
- 集合法
- 字典法
- 性能比较
第四节 上机实验
一、Python 内置类型常见操作的性能验证
- 验证 Python 列表按索引取值操作的时间性能为 O ( 1 ) O(1) O(1)。
- 验证 Python 字典的元素都区和写入操作的时间性能为 O ( 1 ) O(1) O(1)。
- 验证 Python 列表排序操作的时间性能为 O ( n ) O ( n l o g 2 n ) O(n)~O(nlog_2 n) O(n) O(nlog2n)。
二、列表生成方法的性能比较
- 设计至少 4 种不同的方法生成长度为 n 的列表,分析各算法的时间复杂度。
- 编写程序统计 n 为不同规模时各个算法所花费的时间,对数据进行统计分析,讨论实验数据与理论分析是否一致。
三、变位词判断算法的性能比较
- 设计 3 种不同的算法,判断两个给定的字符串是否互为变位词。如果一个字符串是另一个字符串中各字符的重新排列组合,那么这两个字符串互为变位词。为简化问题,假设字符串中的字符由 26 个小写字母组成。算法接受两个字符串作为参数,返回 True 或 False。分析各个算法的时间复杂度。
- 设计测试程序,分别调用以上算法,获得绝对运行时间,分析实验数据与理论分析的一致性。提示(为了使实验结果能明显地区分除不同算法的时间性能,应对字符串的长度取不同的值,例如10、100、1000、10000等;为避免人工输入,应设计辅助的程序自动产生随机字符串;为了公平比较算法的性能,请注意 3 个算法的实验输入数据的一致性)
四、哥德巴赫猜想问题
验证哥德巴赫猜想:任何一个大于 2 的偶数均可以表示为两个素数之和。输入一个大于 2 且小于 100000 的偶数 n,输出所有和为 n 的素数对,并给出总的素数对数量。例如,10 可以表示为素数对 3、7 及 5、5 之和,总共有两对素数。