数据结构与算法 之 复杂度(python版)
Posted Coderusher
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法 之 复杂度(python版)相关的知识,希望对你有一定的参考价值。
@[toc]
一、前言
1984年被授予图灵奖的计算机编程领域的祖师爷 尼古拉斯•威茨(Niklaus Wirth),有一句名言在计算机领域人尽皆知,那就是:
关于公式中的三个元素含义可以对照下表
元素 | 含义 |
---|---|
程序 | 特定问题解决方案的具体实现 |
算法 | 解决特定问题的有限求解步骤(思想) |
数据结构 | 数据与数据之间的结构(逻辑)关系 |
关于这句名言有不少争论,感兴趣的可以移步: "算法+数据结构=程序"过时了吗?
二、正文
1 复杂度
1.1 什么是复杂度?
公式表示:
其中:
字符 | 含义 |
---|---|
$T_(n)$ | 表示代码执行的时间 |
$n$ | 表示数据规模大小 |
$f_(n)$ | 表示每行代码执行的次数总和 |
$O(f_(n))$ | 表示代码的执行时间T(n)与f(n)表达式成正比 |
- 举个栗子
def sumOfN(n):
theSum = 0
for i in range(1, n+1):
theSum += i
return theSum
上述代码的目的是为了计算前 n 个整数累计求和的结果,怎么计算复杂度呢?
在python中,度量复杂度的指标可以选取赋值语句的执行次数
则有
当计算规模 $n -> ∞$ ,得到该代码的复杂度为:
可以看出, $T(n)$ 的精确值并不重要,最终决定 $T(n)$ 的是增速最快的主导部分。
那么,下式的复杂度为多少呢?
很显然,答案是: $O_(n^2)$
1.2.复杂度分析法则
- 单段代码看高频:比如循环。
- 多段代码取最大:比如一段代码中有单循环和多重循环,那么取多重循环的复杂度。
- 嵌套代码求乘积:比如递归、多重循环等
- 多个规模求加法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加。
1.3 常见的大O数量级函数
- 当 n 较小时,难以确定其数量级
- 当 n 增长到较大时,容易看出其变化数量级
$f(n)$ | 名称 |
---|---|
$1$ | 常数 |
$log(n)$ | 对数 |
$n$ | 线性 |
$n*log(n)$ | 对数线性 |
$n^2$ | 平方 |
$n^3$ | 立方 |
$2^n$ | 指数 |
变化曲线如图
1.4 变位词
为了对于空间复杂度有更好的理解,下面引出 ”变位词“
例如 heart和earth,python和typhon
现在有一个题目,要求:写一个函数,以两个词作为参数,返回这两个词是否为变位词
解法1:逐字检查
- 实现思路
- 具体代码
def anagramSolution1(s1,s2): alist = list(s2) # 复制s2到列表 pos1 = 0 still0K = True while pos1 < len(s1) and stillOK: # 循环s1的每个字符 pos2 = 0 found = False while pos2 < len(alist) and not found: if s1[pos1] == alist[pos2]: # 在s2逐个对比 found = True else: pos2.=pos2.+.1 if found: alist[pos2] = None # 找到,打勾 else: stillOK = Falsepos1 = pos1 +1 # 未找到,失败 return still0K
print(anagramSolution1( abcd , dcba ) )
- 代码规模计算:
> 外层循环遍历s1每个字符,将内层循环执行n次而内层循环在s2中查找字符,每个字符的对比次数,分别是1、2…n中的一个,而且各不相同
两重**循环**,使用乘积来进行计算,所以总执行次数为:
> $∑_i=1^n i = n(n+1)/2 = 1/2 n^2 + 1/2 n -> O(n^2)$
#### 解法2:排序比较
- 实现思路
> 将两个字符串都按照字母顺序排好序再逐个字符对比是否相同,如果相同则是变位词有任何不同就不是变位词
![image.png](https://s2.51cto.com/images/20220827/1661585455903920.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
- 具体代码
```python
def anagramSolution2(s1,s2):
alist1 = list(s1) # 转为列表
alist2 = list(s2)
alist1.sort( ) # 分别排序
alist2.sort( )
pos = 0
matches = True
while pos < len(s1) and matches:
if alist1[pos] == alist2[pos]:
pos = pos + 1
else:
matches = False # 逐个对比
return matches
print(anagramSolution2( abcde , edcba ) )
- 代码规模计算:
解法3:暴力枚举
- 实现思路
暴力枚举往往算作下策,因为 $n!$ 的增长速度是大于 $2^n$ 的
因此暴力枚举恐怕不能算是个好算法
解法4:计数比较
- 实现思路
def anagramSolution4(s1, s2):
c1 = [0]* 26
c2 = [0]* 26
for i in range(len(s1) ): # 分别都计数
pos = ord( s1[i]) - ord( a )
c1[pos] = c1[pos] +1
for i in range(len(s2) ):
pos = ord(s2[i])_- ord ( a)
c2[pos] = c2 [pos] +1
j = 0
still0K = True # 计数器比较
while j < 26 and still0K:
if c1[j] == c2[j]:
j = i + 1
else:
stillOK = False
return still0K
print(anagramSolution4( " apple, pleap ))
- 代码规模计算:
前两个循环用于对字符串进行计数,操作次数等于字符串长度n
第3个循环用于计数器比较,操作次数总是26次
所以总操作次数为
其数量级为
这是一个线性数量级的算法,也就是说是四种解法中性能最优的,但我们就应该选择它吗?
1.5 算法的选择
以上是关于数据结构与算法 之 复杂度(python版)的主要内容,如果未能解决你的问题,请参考以下文章