📘 CodingMarble Learn

算法复杂度:算法的工作量是怎样增长的?

同一个问题可以用很多种算法解决,但有的算法需要多得多的步骤。我们不用秒表计时,而是数基本步骤的个数,看它随输入规模n变大时怎样增长。大O表示法给增长速度起了名字:O(1)是常数阶,O(log n)是对数阶,O(n)是线性阶,还有O(n log n)和O(n²)平方阶。顺序查找是O(n),二分查找是O(log n);冒泡排序是O(n²),归并排序是O(n log n)。算法用掉的内存叫空间复杂度。

🎬 分步故事

  1. 在16个盒子里找数字13,一个一个地打开。每打开一个盒子就是一步。这就是顺序查找:最多要n步。
  2. 如果盒子是按顺序排好的,就先打开中间那个,把不可能的那一半扔掉,再重复。二分查找只用4步就找到了13。
  3. 现在比较五种算法,设n = 16。柱子的高度表示步数。有的一直很矮,有一个特别高。
  4. 把输入从8加倍到16。O(n)变成2倍,O(n²)变成4倍,而O(log n)只多了1步。
  5. 给1000个数据排序:冒泡排序大约要一百万次比较,归并排序只要大约一万次。数据很大时,增长速度最重要。
  6. 轮到你了:把n滑块从2拖到1024,看看哪根柱子升得最快。

提示:拖动3D画面可以旋转,用两根手指可以缩放。

🤔 常见疑问,一次讲清

为什么不直接用秒表给程序计时?

时间会因电脑不同而变化,步数不会。第1步数的是打开的盒子个数,不是秒数。

二分查找为什么可以跳过一半的盒子?

因为盒子是按顺序排的。如果中间的数比目标小,它左边的数也都比目标小,所以它们都不可能是答案。看第2步里灰色的盒子。

为什么大O里要去掉常数?

大O关心的是工作量增长得多快。n加倍时,2n和n都变成2倍,所以它们的增长方式一样。第4步展示了加倍。

O(n²)总是比O(n log n)慢吗?

n很小时它甚至可能更快,但随着n变大,n²很快就超过了。第5步里n = 1000时,差距大约是100倍。

这里的log n到底是什么意思?

log₂ n是n在减到1之前可以对半分的次数。1024是10次。拖动最后一步的滑块,看蓝色柱子在n每加倍时只增加1。

同一个问题,有很多种算法

算法就是解决问题的一组精确步骤。大多数问题都不止一种算法能解决。比如在名单里找一个名字,你可以把每个名字都看一遍,也可以(如果名单已排好序)不断把名单对半砍掉。

两种方法答案都对,区别在于效率:各自需要多少工作量和内存。好的程序员会选择数据变大后依然很快的算法。

怎样比较算法用的时间

用秒表计时并不公平:电脑快,慢算法也会显得不错。所以我们数基本步骤(比较、交换、加法),把它写成输入规模n的函数。

最好、平均和最坏情况

最好情况是最走运的输入(13就在第一个盒子里:1步)。最坏情况是最倒霉的输入(13根本不在里面:n步)。我们通常说最坏情况,因为它是一个保证:算法永远不会比这更慢。

时间复杂度和空间复杂度

时间复杂度说明步数怎样随n增长。空间复杂度说明额外内存怎样随n增长。归并排序很快,但需要额外内存;冒泡排序几乎不需要额外内存,但很慢。

大O表示法

大O描述增长的速度,忽略小细节。我们只保留最大的那一项,并去掉常数:3n² + 5n + 2 写成O(n²),因为n很大时,n²那一项几乎占了全部。

大O名称n = 16n = 1000例子
O(1)常数阶11读取数组的第5项
O(log n)对数阶4约10二分查找
O(n)线性阶161000顺序查找、找最大值
O(n log n)n log n阶64约10,000归并排序
O(n²)平方阶2561,000,000冒泡排序、嵌套循环

看代码的快速规则:一个循环走n个数据是O(n);循环里再套一个循环是O(n²);每次把问题砍掉一半是O(log n)。

顺序查找和二分查找的效率

顺序查找一个一个地检查。最坏情况:比较n次,所以是O(n)。无论列表有没有排序都能用。

二分查找需要已排序的列表。先看中间那个;如果它太大,就扔掉右半边,否则扔掉左半边。每一步都让列表减半,所以最坏情况大约是log₂ n + 1次比较:O(log n)。对于1,000,000个数据,大约只要20步,而不是1,000,000步。

排序算法的效率

冒泡排序、插入排序和选择排序都是循环里套循环,所以大约要n²/2次比较:O(n²)。插入排序在最好情况(列表本来就排好了)下是O(n)。

归并排序大约把列表对半分log₂ n次,每一层大约做n的工作量:O(n log n)。它需要O(n)的额外内存。

二分查找需要排好序的列表。如果只查一次,先排序(n log n)比一次顺序查找(n)花得更多。如果要查很多次,排一次序就划算了。

前置条件、后置条件和递归的陷阱

前置条件是算法开始前必须成立的事(二分查找:列表已排序)。后置条件是算法结束时保证成立的事(排序:每个数据都小于或等于后一个)。把它们写下来,有助于测试和证明算法。

递归是指函数在更小的问题上调用它自己。常见错误:

动手试试:让两种查找比赛

在纸条上写下1到32的数字,按顺序正面朝下摆好。让朋友选一个秘密数字。先一张一张地翻,数一数翻了几张。再换成每次都翻中间那一张。重复5次。哪种方法从来不需要翻超过6次?用最后一个3D步骤里的滑块检查一下(n = 32:log₂ 32 = 5)。

重点公式和概念

例题讲解

1. 一份名单有50个名字。顺序查找在最好和最坏情况下各需要几次比较?

最好情况:名字排第一 → 1次比较。最坏情况:名字在最后或根本不在 → 50次比较。顺序查找是O(n)。

2. 对1024个数据的已排序列表,二分查找最多需要几次比较?

每一步让列表减半:1024 → 512 → 256 → 128 → 64 → 32 → 16 → 8 → 4 → 2 → 1。一共减半10次,再加最后一次检查:最多11次比较(log₂ 1024 = 10)。

3. 求f(n) = 4n² + 10n + 7的大O。

保留最大的项(4n²),去掉常数4:O(n²)。

4. 一个循环里i从1走到n,里面另一个循环里j从1走到n。里面那一行代码一共运行多少次?

i的每个值运行n次,共n个值:n × n = n²。时间复杂度O(n²)。

5. 一个O(n²)的程序排1000个数据用2秒。排3000个数据大约要多久?

n变成3倍,所以n²变成3² = 9倍:大约2 × 9 = 18秒。

6. 比较n = 1000个数据时的冒泡排序和归并排序。

冒泡排序:约n²/2 = 500,000次比较。归并排序:约n log₂ n = 1000 × 10 = 10,000次。归并排序的工作量少约50倍,但需要额外内存。

常见错误

练习测验

1. 顺序查找最坏情况的时间复杂度是多少?
2. 二分查找只有在列表满足下面哪个条件时才能用?
3. 如果n加倍,O(n²)的算法大约需要:
4. 下面哪种排序是O(n log n)?
5. 7n + 300的大O是:

练习:自己动手回答

输入或选择你的答案,然后点“检查”。卡住了就看提示;回答后会显示完整解答。

常见问题

用简单的话说,什么是时间复杂度?

它说明输入变大时,算法需要的步数怎样增长。例如O(n)的意思是:输入加倍,步数也加倍。

时间复杂度和空间复杂度有什么区别?

时间复杂度衡量步数;空间复杂度衡量额外内存。算法可以很快但用很多内存,比如归并排序。

哪个大O最快?

O(1)(常数阶)最好,然后是O(log n)、O(n)、O(n log n)、O(n²),指数阶O(2ⁿ)是常见几种里最差的。

哪里会学到

Canada (Ontario)Grade 12C. Designing Modular Programs
Ukraine11 класAlgorithms
England (GCSE, A level)Year 103.1 Fundamentals of algorithms
USA (Common Core, NGSS, AP)Grade 11Selection and Iteration
USA (Common Core, NGSS, AP)Grade 11Algorithms and Programming
South Korea고등학교 2학년Algorithms and programming
South Korea고등학교 3학년Abstraction and algorithms

先学这些

接着学