算法复杂度

时间复杂度

一个算法花费的时间,与算法中语句的执行次数成正比例。哪个算法中语句执行次数多,它花费时间就多。我们把一个算法中的语句执行次数称为 语句频度时间频度,记为 T(n)。这里的 n 表示问题的规模。当 n 不断变化时,时间频度 T(n) 也会不断变化。算法的时间复杂度是指执行算法所需要的计算工作量。

我们想知道 T(n) 变化时所呈现的规律,因此引入了时间复杂度的概念。一般情况下,算法中基本操作重复执行的次数是问题规模 n 的某个函数,用 T(n) 表示,若有某个辅助函数 f(n),使得当 n 趋近于无穷大时,T(n)/f(n) 的极限值为不等于零的常数,则称 f(n) 是 T(n) 的同数量级函数,记作 T(n)=O(f(n)),故而称 O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。

空间复杂度

空间复杂度是指算法在计算机内执行时所需存储空间的度量,与时间复杂度类似,记作 S(n)=O(f(n))。
算法执行期间所需要的存储空间包括 3 个部分:

  • 算法程序所占的空间
  • 输入的初始数据所占的存储空间
  • 算法执行过程中所需要的额外空间

Big O notation

大 O 符号是一种算法复杂度的相对表示方式。这个句子里有一些重要而严谨的用词:

  • 相对(relative):你只能比较相同的事物。你不能把一个做算数乘法的算法和排序整数列表的算法进行比较。但是,比较 2 个算法所做的算术操作(一个做乘法,一个做加法)将会告诉你一些有意义的东西。

  • 表示(representation):大 O(用它最简单的形式)把算法间的比较简化为了一个单一变量。这个变量的选择基于观察或假设。例如,排序算法之间的对比通常是基于比较操作(比较 2 个结点来决定这 2 个结点的相对顺序)。这里面就假设了比较操作的计算开销很大。但是,如果比较操作的计算开销不大,而交换操作的计算开销很大,又会怎么样呢?这就改变了先前的比较方式。

  • 复杂度(complexity):如果排序 10,000 个元素花费了我 1 秒,那么排序 1 百万个元素会花多少时间?在这个例子里,复杂度就是相对其他东西的度量结果。

123456789012 这两个数字来举例,我们在学校里学到的基本算术操作是:

  • 加法
  • 减法
  • 乘法
  • 除法

它们中每一个都是一次操作或一个问题。为它们求解的方法就被叫做
算法algorithm)。

加法是最简单的了。你把加数排成行,按列加上每个数字,把所加得的数的末位数字写到结果里。所加得的数的十位及其以上的数字转入下一列的计算中。

让我们假设在算法中,加上这些数是计算开销最大的操作。合乎情理的说,为了把这两个数加起来我们必须要加 6 次数字(并且可能进位到第 7 次)。如果我们把两个 100 位数相加,我们必须做 100 次加法操作。如果我们把两个 10,000 位数相加,我们必须做 10,000 次加法操作。

看到这里的模式了吗?复杂度complexity,就是操作的数量),对于加法中较大数的数字个数 n,是直接成比例的。我们称这为 O(n) 或者 线性复杂度(linear complexity)

除了借位替代了进位,减法也是相似的。

乘法就不同了。你把乘数排成行,取放在下面的乘数的第 1 个数字,把它逆序乘以上面乘数的每一个数字。下面乘数的其余数字也这样做。所以为了乘我们的两个 6 位数乘数,我们必须做 36 次乘法操作。我们还需要做 10 或 11 次列的加法操作来得到最终结果。

如果我们有两个 100 位数相乘,我们需要做 10,000 次乘法操作和 200 次加法操作。两个 100 万位数相乘,我们需要做 1 万亿 (1012) 次乘法操作和 200 万次加法操作。

作为 n 平方的算法衡量尺度,这就是 O(n2),即平方复杂度(quadratic complexity)。现在是时候介绍另一个重要概念了:

我们只关心复杂度最重要的部分

敏锐的人可能已意识到,我们可以把操作次数表示为:n2 + 2n。但正如你所看到的,我们的两个 100 万位数相乘的例子,第二个 2n 无关紧要(在那个阶段,2n 只占操作总量的 0.0002%)。

有人注意到我们在这里假设场景为最坏的情况。当我们做 6 位数乘法时,如果其中一个是 4 位数另一个是 6 位数,那么我们只需做 24 次乘法操作。然而,对于那个’n’,我们仍然计算最坏情况,即乘数都是 6 位数的情况。因此,大 O 符号是关于一个算法的最坏情况的。

电话簿例子

假设电话薄把人按这样的顺序排列:姓、缩写或名、地址、然后是电话号码。

现在,如果你要指示计算机在一个包含 1,000,000 个名字的电话簿中查找『史大佗』的电话号码,你会怎么做?忽略也许你能猜测出『史』从电话簿哪里开始的事实(假设你不能猜测),你会怎么做?

一种典型的实现也许是,打开电话簿的正中间,取第 500,000 条记录,把它和『史』进行比较。如果这恰好就是『史大佗』,那我们真幸运。然而,『史大佗』更有可能在其前面或后面。如果在后面,那么我们把电话簿后面一半从中间划分开,然后重复之前的过程;如果在前面,那么我们把第一半从中间划分开,然后重复之前的过程。以此类推。

这种算法叫做二分搜索(binary search)。不论你是否意识到,它在编程中每天都用到。

因此,如果你想要在包含 100 万名字的电话簿中查找一个名字,事实上,通过这种算法,最多 20 次,你能找到任何名字。在比较搜索算法中,我们决定把比较操作作为我们的 ’n’

  • 对于有 3 个名字的电话簿,最多需 2 次比较。
  • 对于有 7 个名字的电话簿,最多需 3 次比较。
  • 对于有 15 个名字的电话簿,最多需 4 次比较。
  • 对于有 1,000,000 个名字的电话簿,最多需 20 次比较。

这简直好得难以置信,不是吗?

用大 O 术语就是 O(log n),即对数复杂度(logarithmic complexity)。现在问题中的对数可以是 ln(底数为 e),log10,log2 或者以其它为底数,这无关紧要,它仍然是 O(log n),正如 O(2n2) 和 O(100n2) 都记为 O(n2)。

现在,值得花时间说明一下,对于算法,大 O 符号能够被用于决定 3 种情况:

  • 最好情况(Best Case):在电话簿的搜索中,最好情况是我们比较了 1 次就找到了名字。这就是 O(1),即常数复杂度(constant complexity)
  • 期望情况(Expected Case):正如上面讨论过的,复杂度是 O(log n)。
  • 最坏情况(Worst Case):也是 O(log n)。

通常我们不关心最好情况。我们对期望和最坏情况感兴趣。有时,期望情况更重要,有时最坏情况更重要。

回到电话簿的例子上来。

如果你有一个电话号码,想要查找名字,要怎么做呢?警察有一个相反(按电话号码排列)的电话簿,但是对于一般公众,这样的查询会被拒绝,是吧?技术上,你能在普通电话簿中查找一个号码。要怎么做呢?

你从第一个名字开始比较号码。如果吻合,很棒,如果不吻合,你移到下一条记录。你必须这样做,因为电话簿是无序(unordered)的(电话号码的排列是无序的)。

因此,查找一个名字:

  • 最好情况(Best Case):O(1)
  • 期望情况(Expected Case):O(n)(对应 500,000)
  • 最坏情况(Worst Case):O(n)(对应 1,000,000)

旅行商例子

这是计算机科学中值得提到的一个相当有名的问题。在这个问题中,有 N 个城镇,每个城镇通过道路与 1 个或多个其它城镇相连,道路的路程是确定的。旅行商问题就是找出访问每个城镇的最短路线。

听起来很简单?再想想。

如果有 3 个城镇 A、B、C,两两之间都有道路,那么你可以这样走:

A -> B -> C
A -> C -> B
B -> C -> A
B -> A -> C
C -> A -> B
C -> B -> A

好吧,事实上,实际路线比上面的少,因为一些路线是等价的(例如,A -> B -> C 和 C -> B -> A 是等价的,因为它们使用同一条路线,只是方向相反)。所以,事实上,这里有 3 条可能的路径。

增加到 4 个城镇,你有 12 条可能的路径(如果我没记错)。5 个城镇,60 条可能的路径。6 个城镇,360 条可能的路径。

这是一个被叫做阶乘(factorial)的数学运算函数。大体上:

5! = 5 * 4 * 3 * 2 * 1 = 120
6! = 6 * 5 * 4 * 3 * 2 * 1 = 720
7! = 7 * 6 * 5 * 4 * 3 * 2 * 1 = 5040
…
25! = 25 * 24 * … * 2 * 1 = 15,511,210,043,330,985,984,000,000
…
50! = 50 * 49 * … * 2 * 1 = 3.04140932 * 1064

所以旅行商问题的大 O 符号表示是 O(n!),即阶乘(factorial)组合复杂度(combinatorial complexity)

当你有 200 个城镇的时候,使用传统计算机,那么全世界已经没有足够的时间来解决这个问题了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • 算法复杂度分析 算法复杂度基本定义 算法复杂度分析基于以下四条定义: 如果存在常数c与$n_{0}$使$N \ge...
    月见樽阅读 637评论 0 0
  • 时间复杂度 时间频度 一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没...
    小慕先森阅读 417评论 0 0
  • 时间复杂度 一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n...
    X_JX阅读 468评论 0 1
  • 本篇博客的目的是总结算法复杂度的概念,分析了几种经典算法的复杂度。 时间复杂度:算法中基本操作重复执行的次数是问题...
    坏科学阅读 334评论 0 0
  • 你在怀念你的爱情吗,你在什么时候觉得你很想她?那些来过的,正在陪你走的,还有在路上的,人们总是谈论过去的日子,...
    小五的阅读 228评论 0 1