本系列是算法通关手册LeeCode的学习笔记
算法通关手册(LeetCode) | 算法通关手册(LeetCode) (itcharge.cn)
本系列为自用笔记,如有版权问题,请私聊我删除。
目录
一,算法复杂度简介
算法复杂度(Algorithm complexity),在问题输入规模为 n 的条件下,程序的时间使用情况和空间使用情况。
这里的问题规模指的是算法输入的数据量大小。
二,时间复杂度
2.1 时间复杂度简介
时间复杂度(Time complexity),在输入规模为 n 的条件下,算法运行所需要花费的时间,可以记为T(n)。
我们将基本操作次数作为时间复杂度的度量标准。
基本操作是指算法运行中的每一条语句,每次基本操作都可以在常数时间内完成,是一个运行时间不依赖于操作数的操作。
如:
def algorithm(n):
fact = 1
for i in range(1, n + 1) :
fact *= 1
return fact
上述算法中所有语句的执行次数加起来1 + n + n + 1 = 2 * n + 2,可以用一个函数 f(n) 来表达语句的执行次数:f(n) = 2 * n + 2
2.2 渐进符号
专门用来刻画函数的增长速度。渐进符号只保留了最高阶幂,而忽略了如低阶幂,系数,常量等函数中增长较慢的部分。
经常用到的渐进符号有三种:Θ 渐进紧确界符号、O 渐进上界符号、Ω 渐进下界符号。
2.2.1 Θ 渐进紧确界符号
对于函数 f(n) 和 g(n),f(n) = Θ(g(n))。存在正常量 c1、c2 和 n0,使得对于所有 n >= n0 时,有 0≤c1⋅g(n) <= f(n) <= c2⋅g(n)。
也就是说,如果函数 f(n) = Θ(g(n)),那么我们能找到两个正数 c1、c2,使得 f(n)被 c1⋅g(n)和 c2⋅g(n)夹在中间。
例如:T(n)=3 * n ^ 2 + 4 * n + 5 = Θ(n^2),可以找到 c1 = 1,c2 = 12,n0 = 1,使得对于所有 n >= 1,都有 n ^ 2 <= 3 * n ^ 2 + 4 * n + 5 <= 12 * n ^ 2。
2.2.2 O 渐进上界符号
对于函数 f(n) 和 g(n),f(n) = O(g(n))。存在常量c,n0,使得当 n > n0时
有0 <= f(n) <= c * g(n)
在这里要注意表示形式,f(n) = O(g(n)) 意为 f(n) 的上界是 g(n)。
2.2.3 Ω 渐进下界符号
对于函数 f(n) 和 g(n),f(n) = Ω(g(n))。存在常量c,n0,使得当 n > n0时
有0 <= c * g(n) <= f(n)。
同样地,f(n) = Ω(g(n)) 意为 f(n) 的下界是 g(n)。
2.3 时间复杂度计算
求解时间复杂度的步骤如下:
1,找出算法中的基本操作(基本语句):算法中执行次数最多的语句就是基本语句,通常是最内层循环的循环体部分;
2,计算基本语句执行次数的数量级:保证函数中的最高次幂正确即可;
3,用渐进上界O来表示时间复杂度。
求解时应注意:
加法原则:总的时间复杂度等于量级最大的基本语句的时间复杂度;
乘法原则:循环嵌套代码的复杂度等于嵌套内外基本语句的时间复杂度乘积。
例如:
# 2.1 时间复杂度简介
# 例1
def algorithm(n):
fact = 1
for i in range(1, n + 1):
fact *= 1
return fact
# 2.3 时间复杂度的计算
# 常数级O(1) :
def Time_O1(n):
a = 1
b = 2
return a + b
# 只要不存在循环或递归结构,时间复杂度都为O(1),O(1)的意义是常数级时间
# 线性O(n)
def Time_On(n):
sum = 0
for i in range(n):
sum += 1
return sum
# 单层循环,且语句的执行次数为n
# 平方O(n^2)
def Time_On2(n):
ans = 0
for i in range(n):
for j in range(n):
ans += i
return ans
# 嵌套循环中,内层执行 j 从0到n-1 即n次,外层同理,根据乘法原则,复杂度为O(n^2)
# 阶乘O(n!)
def Time_On_(start, end, count):
if start == end:
count[0] += 1
return
for i in range(start, end):
Time_On_(start + 1, end, count)
# count = [0]
# Time_On_(1, 6, count)
# print(count[0]) # 输出为120
# 在循环中使用了递归,则在每次递归调用中,循环次数为end - start。
# 在第一层递归调用中,循环次数为end - start,
# 在第二层递归调用中,循环次数为end - (start + 1),
# 以此类推。总的循环次数可以表示为
# (end - start) * (end - (start + 1)) * (end - (start + 2)) * ... * 1,即为n!。
# 因此,count的最终值将等于n!,其中n为end - start的值。
# 对数O(logn)
def Time_Olngn(n):
count = 0
ans = 1
while ans < n:
ans *= 2
count += 1
return count
# 每次ans * 2,即n = ans * (2^count)
# count = logn
# 线性对数O(n * logn)
def Time_Onlogn(n):
ans = 1
count = 0
while ans < n:
ans *= 2
for i in range(n):
count += 1
return count
# 嵌套循环中,内层循环时间复杂度为O(n)
# 外层循环的时间复杂度为O(logn)
# 根据乘法原则,时间复杂度为O(n * logn)
根据从小到大排序,常见的时间复杂度主要有:
O(1) < O(logn)< O(n) < O(n*logn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)。
2.4 最佳,最坏,平均时间复杂度
时间复杂度是关于一个输入问题规模 n 的函数
最佳时间复杂度:每个输入规模下用时最短的输入对应的时间复杂度;
最坏时间复杂度:每个输入规模下用时最长的输入对应的时间复杂度;
平均时间复杂度:每个输入规模下所有可能输入所对应的平均用时复杂度(随机输入下期望用时的复杂度)。
最佳时间复杂度和最坏时间复杂度都是极端条件下的时间复杂度,发生的概率其实很小。
为了能更好的表示正常情况下的复杂度,所以我们一般采用平均时间复杂度作为时间复杂度的计算方式。
三,空间复杂度
3.1 空间复杂度简介
空间复杂度(Space Complexity),在问题输入规模为 n 的条件下,算法所占用的空间大小,可以记作 S(n)。一般将算法的辅助空间作为衡量空间复杂度的标准。
相比于算法的时间复杂度的计算来说,算法的空间复杂度更容易计算,主要包括
局部变量(算法范围内定义的变量)所占用的空间
和
系统为实现递归所使用的栈空间两个部分。
3.2 空间复杂度计算
# 常数O(1)
def Space_O1(n):
a = 1
b = 2
ans = a * b + n
return ans
# 空间占用大小为常数阶,所以空间复杂度为O(1)
# 线性O(n)
def Space_On(n):
if n <= 0:
return 1
return n * Space_On(n - 1)
# 采用了递归,每次递归调用占用了一个栈空间,调用了n次,所以空间复杂度为O(n)
根据从小到大排序,常见的算法复杂度主要有:
O(1) < O(logn) < O(n) < O(n^2) < O(2^n)
四,总结
算法复杂度包括 【时间复杂度】和【空间复杂度】,用来分析算法执行效率与问题输入规模n的增长关系。通常采用【渐进符号】的形式来表示算法复杂度。
原文内容在这里,如有侵权,请联系我删除。