基本概念
数据结构的基本概念
数据
信息的载体。计算机程序加工的原料。
数据元素
数据的基本单位;由若干数据项组成;
数据对象
相同性质的数据元素的集合;
数据类型
值的集合和此集合的一组操作的总称。 以bool为例:值的集合:true和false;操作:与、或、非
- 原子类型:不可再分:如int、bool;
- 结构类型:可以再分,如struct;
- 抽象数据类型:抽象数据组织和相关操作。ADT,只考虑结构和运算,不考虑存储结构。
数据结构
相互之间存在一种或多种特定关系的数据元素的集合。数据结构只关心数据之间的关系,不关心内容。
数据结构的三要素
逻辑结构
描述元素之间的逻辑关系,独立于计算机。
- 集合:没有什么关系
- 线性结构:一对一
- 树形结构:一对多
- 图状结构:多对多
存储结构
逻辑结构的计算机实现。
顺序存储必须连续,非顺序存储可以离散;存储结构影响空间分配的方便程度(增删改);存储结构影响对数据的运算(查询);
- 顺序存储
- 链式存储
- 索引存储
- 散列存储
运算
包括定义和实现;定义针对逻辑结构,表示功能(增删改查);实现针对存储结构
算法
算法的概念
程序=数据+算法
- 数据:描述问题,存入计算机;
- 算法:高效处理数据,是求解问题的步骤;
算法的五大特性:
- 有穷性:算法必须有穷,程序可以无穷
- 确定性:相同输入必须相同输出
- 可行性
- 输入:有零个或多个输入
- 输出:有一个或多个输出
时间复杂度
评价时间开销与问题规模n之间的关系。
时间复杂度的计算
语句的频度就是该语句在算法中被重复执行的次数。算法中所有语句的频度之和就是T(n)。时间复杂度就是分析
计算步骤:
- 找到一个基本操作:基本操作指的是最深层循环;
- 分析该基本操作的执行次数x与问题规模n的关系
- x的数量级
就是算法时间复杂度
常用技巧
- 顺序执行的代码只影响常数项,可以忽略;
- 只需要挑循环中的一个基本操作,分析它的执行次数x与n的关系;
- 多层嵌套循环,只关注最深层循环循环了几次;
加法规则:只保留最高阶,且系数变为1。
乘法规则:多项相乘,都保留
时间复杂度常见大小排序:常对幂指阶
时间复杂度不仅依赖问题的规模,还与输入数据的性质有关。可以分为最好时间复杂度、平均时间复杂度、最坏时间复杂度。一般只考虑最坏时间复杂度。
例题
1 | //例1: |
第一句顺序执行代码走1次,深层循环内执行了2n次,所以T(n)=2n+1。根据加法规则,只保留最高阶,且系数变为1.
1 | //例2: |
最深层循环走
1 | //例3 |
设最深层循环的语句频度为
1 | //例4 |
设执行次数为t,则
1 | // 例5 |
因为是乱序存放。所以要考虑不同情况:
- 最好时间复杂度:元素n在第一个位置。
; - 最坏时间复杂度:元素n在最后有个位置。
- 平均时间复杂度:元素n在任意一个位置的概率相同,都是
。
1 | //例6 |
内层循环j<=n与外层循环的变量无关,各自独立。所以对于内层循环来说,每次内层循环都执行n次。
对于外层循环k<=n,假设循环t次,则循环结束时满足
根据乘法原则,T(n)为内层循环乘外层循环,得
1 | //例7 |
拆基本运算sum += ++i,等价为++i;sum=sum+i; 每执行一次循环,i增1.
i=1时,sum=0+1;
i=2时,sum=0+1+2;
i=3时,sum=0+1+2+3;
所以sum=
1 | //例8 |
先拆外层循环,每次自乘2,所以当循环k次后,
对于内层循环,i取每一个值,内层j都有i个取值,所以内层只需要把每层值相加即可:
- i=1时,内层循环1次
- i=2时,内层循环2次,总共循环1+2
- i=3时,内层循环3次,总共循环1+2+3
- i=k时,内层循环k次,总共循环1+2+3+...+
,用等比数列求和公式,总共有 次
所以当外层循环k次时,内层循环执行
//例9
设
循环的时间复杂度总结
- 每一层循环变量都是++:将每一层变量的最大遍历次数相乘
- 不是每一层循环的变量都是++:对于外层循环每一个值i,求出内层遍历次数
,然后求和
1 | //每一层循环变量都是++: |
1 | //不是每一层循环的变量都是++: |
空间复杂度
评价空间开销与问题规模n之间的关系。
- 普通程序:
- 找到所占空间大小与问题规模相关的变量;
- 分析所占空间x与问题规模n的关系
; - x的数量级
就是算法的空间复杂度S(n);
- 递归程序:
- 找到递归调用的深度x与问题规模n的关系
; - x的数量级
就是算法的空间复杂度S(n); - 注:考研大概率空间复杂度就是递归调用的深度。但有的算法各层函数所需存储空间不同,分析方法略有区别
- 找到递归调用的深度x与问题规模n的关系
- 空间复杂度也遵循加法规则、乘法规则
1 | void test(int n) |
这个代码无论问题规模n有多大,算法所需要的运行空间都是固定的常量。算法空间复杂度是
1 | void test2(int n) |
1 | void test3(int n) |
1 | void test4(int n) |
flag[n]的空间大小,会随着递归而发生变化。是个等差数列。