-效率和增长量级 Lecture 9 - Efficiency and Orde

官方信息技术老师·19 页·深入(追求细节与边界)·0 次浏览·2 天前
算法效率复杂度增长量级Big O

效率和增长量级

看完你能独立分析算法增长量级并说清O(n)与O(log n)的本质差异

按 空格/→ 演示下一步

1 / 19 页

全部页面点击任意一页,跳回舞台从这页播放

算法效率复杂度增长量级Big O

效率和增长量级

看完你能独立分析算法增长量级并说清O(n)与O(log n)的本质差异

1第 1 页 · 效率和增长量级

为什么要关注效率

从一摞电话本里找一个人——你可以从第一页翻到最后一页,也可以二分定位。前者翻完一千页,后者最多翻十页。这就是效率。

效率定义
同样输入下,算得快、占内存少、步骤少,就是效率高
规模放大效应
数据从100涨到100万,线性慢1万倍,平方慢1亿倍
常数也致命
同样的 O(n),常数因子差10倍,亿级数据上就是数量级差距
查字典对应 →顺序 vs 二分查找

同样的词,顺序翻一万页 vs 二分翻十四页,效率天差地别

2第 2 页 · 为什么要关注效率

搜索算法的代价对比

前面我们说效率问题很重要。这一页直接上数字:在一百万条数据里找一个东西,线性扫描要查 100 万次,二分查找只要 20 次。

O(n) 线性扫描
从头到尾逐个检查,最坏 n 步才能找到
O(log n) 二分查找
每次把候选范围砍半,最坏 ⌈log₂n⌉ 步
数字对照
n=10³→10 步;n=10⁶→20 步;n=10⁹→30 步
增长的本质
n 翻一倍,O(n) 工作量翻倍;log n 只多 1 步
猜数字游戏对应 →二分查找

心里想 1–1000,你说 500,提示后范围直接砍半

$T_{linear} = n, \quad T_{binary} = \lceil \log_2(n+1) \rceil$
3第 3 页 · 搜索算法的代价对比

什么是渐近记号

上一页我们看到线性搜索是 O(n),二分搜索是 O(log n)。这个 'O' 究竟代表什么?为什么我们只关心它而不关心具体数字?

渐近行为
关注 n 趋近无穷大时的趋势,常数和低阶项都被舍弃
大 O 上界
算法代价「不超过」这个增长量级
大 Ω 下界
算法代价「至少」是这个增长量级
大 Θ 紧界
上下界重合,才是算法的真正量级
山顶俯瞰城市对应 →渐近记号

站得够远,单栋楼消失只剩天际线轮廓——渐近就是「站到无穷远看趋势」

f(n)=O(g(n))    c>0, n0>0, nn0: f(n)cg(n)f(n) = O(g(n)) \iff \exists\, c>0,\ \exists\, n_0>0,\ \forall\, n \geq n_0:\ f(n) \leq c \cdot g(n)
4第 4 页 · 什么是渐近记号

Big-O 的图形化理解

先看上方的 c·g(n) 与下方的 f(n),再看 n₀ 之后谁压制谁。

图解渲染中…
c1n₀ 阈值,仅此点之后才要求 f(n) ≤ c·g(n)a1候选上界 g 与常数 c,c 可调大、g 可换高阶b1真实增长曲线,能否被上方压制决定 O 关系
5第 5 页 · Big-O 的图形化理解

Big-O 的形式化定义

数学定义:∀n ≥ n₀,0 ≤ f(n) ≤ c·g(n),附日常语言翻译

Big-O 的形式化定义
数学定义:∀n ≥ n₀,0 ≤ f(n) ≤ c·g(n),附日常语言翻译
6第 6 页 · Big-O 的形式化定义

Big-Ω 与 Big-Θ

Big-O 画了天花板——算法不会比这更慢。但最少要花多少?天花板下面,地板在哪里?这就是 Big-Ω 和 Big-Θ 要回答的问题。

Big-Ω 下界
算法在最坏情况下也至少要花这么多,再压也压不下去
Big-Θ 紧确界
上下界恰好合一,增长量级被唯一确定
O/Ω/Θ 关系
O 封顶、Ω 封底,Θ 是两者同时成立的交集
房间的天花板与地板对应 →O 与 Ω 的上下界

天花板=O 封顶,地板=Ω 封底,两者合拢=Θ 严丝合缝

T(n)=Ω(f(n))    c>0,n0:nn0, T(n)cf(n)T(n)=Θ(f(n))    c1,c2>0,n0:nn0, c1f(n)T(n)c2f(n)T(n)=\Omega(f(n))\iff\exists c>0,n_0:\forall n\ge n_0,\ T(n)\ge c\cdot f(n)\\\\T(n)=\Theta(f(n))\iff\exists c_1,c_2>0,n_0:\forall n\ge n_0,\ c_1 f(n)\le T(n)\le c_2 f(n)
7第 7 页 · Big-Ω 与 Big-Θ

记号使用的常见误区

上一页我们认识了 O、Ω、Θ 三个记号。但第一次写 f(n)=O(n) 时,很多人会犹豫:这个等号真的能用吗?它其实是个伪装成等号的「属于」关系,许多细节都藏在里面。

等号是单向的
f=O(n) 表示 f 属于集合 O(n),不能反过来写 O(n)=f
O 本质是集合
O(n) 装的是所有不超过 c·n 的函数,不是某条具体曲线
常数因子被吞
50n 和 n 增长同速,差一个常数对量级没影响
低阶项被吞
n² + 100n 中,n 项跑得再快也追不上 n²,被忽略
招聘要求「本科及以上」对应 →f(n) = O(n)

上界是门槛不是等于;硕士、本科都满足

8第 8 页 · 记号使用的常见误区

复杂度类全景图

从左到右代价越来越大:绿色放心、黄色工程常态、红色小心、黑色基本不可用。

图解渲染中…
a4高效排序如归并、快排常见复杂度a7暴力穷举典型代价,n 只能到 20~30a8全排列类问题,n=20 已约 2.4×10¹⁸
9第 9 页 · 复杂度类全景图

常数 O(1) 与对数 O(log n)

从搜索对比那页我们看到:有些操作几乎不随数据量变化,有些每翻倍只多一步。这两种增长方式就是 O(1) 和 O(log n)。

常数复杂度
操作耗时与 n 无关,10 个和 10 亿个用时一样
对数复杂度
每步将候选范围减半,n 翻倍只多一步
隐藏的常数因子
O(1) ≠ 零耗时;对数底数在大 O 下可忽略
典型实现来源
O(1) 来自哈希/数组寻址;O(log n) 来自二分/平衡树
字典查字对应 →常数与对数复杂度

已知页码直接翻是 O(1);从中间二分逼近是 O(log n)

n=2kk=log2nn = 2^k \Rightarrow k = \log_2 n
10第 10 页 · 常数 O(1) 与对数 O(log n)

线性 O(n) 与线性对数 O(n log n)

上页 O(log n) 每次砍半极快,但现实中很多事不能只砍半。比如全班点名必须每个人都叫到;归并排序又得反复拆合。今天看两种典型节奏:O(n) 与 O(n log n)。

线性 O(n)
遍历一遍,每个元素恰好访问一次,如求最大值
线性对数 O(n log n)
分治策略,log n 层各处理 n 个元素,如归并排序
数量级对比
n=10⁶ 时,O(n) 约 10⁶,O(n log n) 约 2×10⁷
本质区别
O(n) 问题不可分;O(n log n) 能拆 log n 层递归
淘汰赛赛制对应 →分治策略 O(n log n)

log n 轮比赛,每轮所有 n 个选手都上场

T(n)=2T(n/2)+nO(nlogn)T(n) = 2T(n/2) + n \Rightarrow O(n\log n)
11第 11 页 · 线性 O(n) 与线性对数 O(n log n)

多项式与指数爆炸

对数、线性、线性对数都还「友好」——n 涨 10 倍,计算量涨 10 倍以内。但从平方开始曲线变陡;到了指数,直接「爆炸」。看 O(n²) 和 O(2ⁿ),找到可接受到不可行的边界。

O(n²) 平方级
n 千内飞快,万级仍可,百万级崩盘
O(2ⁿ) 指数级
n 每加 10,计算量约乘 1000 倍
可行边界
n≈25 勉强能跑,n>40 等不到结果
增长鸿沟
多项式与指数之间是断崖,没温和过渡
棋盘放米对应 →指数压顶效应

第64格的米粒数比前63格加起来还多——指数独有的「压顶」特征

230109, 2501015;n=106n2=10122^{30}\approx 10^9,\ 2^{50}\approx 10^{15};\quad n=10^6\Rightarrow n^2=10^{12}
12第 12 页 · 多项式与指数爆炸

O(n!) 的恐怖增长

O(2^n) 已经够恐怖,O(n!) 还要再上一个量级——这是旅行商问题在 n>20 时彻底崩溃的根源。

O(2^n) 指数增长
  • 每加深一层,选项数翻倍:2, 4, 8…
  • n≈30 已逼近现代算力极限
  • 子集枚举、汉诺塔经典递归
O(n!) 阶乘增长
  • 逐项连乘:1, 2, 6, 24, 120…
  • n≈20 就已实际不可解
  • 旅行商(TSP)全排列暴力解
n! 比 2^n 更绝望:每多一个元素,选项数乘以当前 n(而非固定翻倍),因此 20 元素的全排列就突破算力边界。
13第 13 页 · O(n!) 的恐怖增长

单层循环的复杂度

从一段单层 for 循环开始,逐步推出它的时间复杂度。

1
识别循环边界
看清 i 的起止范围,圈数由 n 决定
2
计算执行次数
循环体执行 n 次,与 n 一一对应
3
累加每次代价
假设循环体每次耗时 c(常数操作)
4
求和得表达式
T(n) = c·n,是 n 的线性函数
5
大O化简
去掉常数 c,得 T(n) = O(n)
14第 14 页 · 单层循环的复杂度

嵌套循环的复杂度

从循环执行次数出发,依次分析乘法规律、对数陷阱与边界条件。

1
固定两层
外层 n 次、内层固定 n 次,计数为 n×n=n²
2
层数推广
每层固定 n 次,k 层相乘;两层为 n²,三层为 n³
3
对数依赖
内层反复乘 2 时,每步约做 log n 次,合计 n log n
4
指数陷阱
若内层次数按 1、2、4 翻倍,总和为 2^(n+1)-1,属于 O(2^n)
5
检查边界
上下界相关、提前 break 或循环分开时,应按实际计数判断
15第 15 页 · 嵌套循环的复杂度

递归函数的复杂度分析

python

归并排序的递归结构与递归树追踪:T(n) = 2T(n/2) + n,每层代价如何累加成 n log n

代码高亮加载中…

L5-L6 两次递归合起来是 2T(n/2),L7 的 merge 给本层加上 n 的代价;tree() 把递归树打出来,每层 cost=n,log n 层相加就是 O(n log n)

16第 16 页 · 递归函数的复杂度分析

主定理:递归复杂度速解公式

上一页我们手撕递归树的层级累加,其实有套路。T(n) = aT(n/b) + f(n) 这一族递归,主定理给了一份三档速判表:先算临界函数,再看 f(n) 落在哪边。

临界函数
先算 n^log_b a,所有判定都围绕 f(n) 与它的大小比较
Case 1: f 更小
f = O(n^(log_b a - ε)),叶子层累加主导,T = Θ(n^log_b a)
Case 2: 两者同阶
f = Θ(n^log_b a · log^k n),多乘一个 log^(k+1) n
Case 3: f 更大
f = Ω(n^(log_b a + ε)),需满足正则条件,根层主导,T = Θ(f)
医生看诊分轻重对应 →主定理三档判定

临界函数像量体温,f(n) 比它高/低/平,对应开三种复杂度药方

f(n) vs nlogba:Θ(nlogba), Θ(nlogbalogk+1n), Θ(f(n))f(n) \text{ vs } n^{\log_b a}: \ll \to \Theta(n^{\log_b a}),\ \approx \to \Theta(n^{\log_b a}\log^{k+1}n),\ \gg \to \Theta(f(n))
17第 17 页 · 主定理:递归复杂度速解公式

核心要点回顾

  • Big-O 是增长的上界,不等于真实运行时间
  • 复杂度层级间是数量级鸿沟,常数优化无法跨越
  • 判定核心:找循环嵌套与递归分支的展开结构
  • O 是上界、Ω 是下界,Θ 才是紧确等价
  • n 小时常数关键,n 大时量级决定一切
延伸主题:均摊分析空间复杂度P 与 NP 问题
18第 18 页 · 核心要点回顾

课后思考

先独立思考,再翻看参考答案。三问覆盖回顾、应用、延伸三个层次。

1复杂度更低的算法就一定跑得更快吗?除 Big-O 之外,还有哪些'看不见'的因素在悄悄决定真实速度?

参考答案Big-O 只刻画增长趋势,忽略常数与硬件细节。当数据规模不大时,O(n²) 甚至可能跑赢 O(n log n);缓存命中率、数据有序性、库的成熟度都会左右真实表现。

2哈希表用 O(n) 额外空间换 O(1) 查找。类似的'空间换时间'思路,还能把哪些常见操作的时间复杂度再压一压?

参考答案核心是'预先索引化':前缀和数组把区间求和从 O(n) 压到 O(1),位图把查重从 O(n) 压到 O(1),布隆过滤器用更少空间做判重。思路都是把信息提前算好。

3旅行商等组合优化问题至今找不到多项式时间算法。它们到底'难'在哪里?P 和 NP 的区分究竟意味着什么?

参考答案P 类是多项式时间内可求解的问题,NP 类是多项式时间内可验证的问题。旅行商等组合问题目前找不到多项式算法,也无人能证明其不存在。P 是否等于 NP,是 CS 最大的开放谜题。

19第 19 页 · 课后思考