用大O表示算法时间复杂度的歧义

简介: ## 例题 OK, 长话短说,看一下这个函数的时间复杂度是多少? ```C void foo1(int x) { int bar = 0; for (int i = 0; i < x; i++) { bar++; } } ``` 显然,这个函数输入x,然后函数就循环x次,那就是`O(n)`咯。 此题的来源是考研408里的

例题

OK, 长话短说,看一下这个函数的时间复杂度是多少?

void foo1(int x) {
    int bar = 0;
    for (int i = 0; i < x; i++) {
        bar++;
    }
}

显然,这个函数输入x,然后函数就循环x次,那就是O(n)咯。

此题的来源是考研408里的一道选择题,并做了简化:

void foo2(int n) {
    int sum = 0;
    int i = 0;
    while (sum < n) {
        sum += i++;
    }
}

此题问时间复杂度是多少,显然,命题人希望的答案是O(根号n)

但事实上,真这么简单么?

剧情反转

再来看这个函数:

void foo3(int[] arr) {
    int bar = 0;
    for (int i = 0; i < arr.length; i++) {
        bar++;
    }
}

对于foo3,应该没有争议,时间复杂度是O(n)

但是显然,这两个函数输入的数据量完全不一样,并且实际在机器上运行所耗费的时间也完全不同:foo1只要输入一个大的整数,运行的时间就会比输入上亿规模数组的foo3还慢。它们真的都是O(n)的时间复杂度?显然foo1的时间复杂度应该远高于foo3啊。

看到这里,肯定开始觉得矛盾和疑惑了,再举个例子,让你更纠结。

素数判定问题是一个已知的NP-Hard问题,求解这个问题的一个直观算法是:

bool isPrime(int n) {
    // n > 2
    for(int i = 2; i < n; i++){
        if(n % i == 0) {
            return false;
        }
    }
    return true;
}

如果foo1的时间复杂度是O(n)的话,isPrime的复杂度也毫无疑问应该是O(n)

OK,一个NP-Hard问题就这么轻易地被多项式时间算法解决了,P = NP,图灵奖诞生了!

争议点

其实,引起上述悖论的根源,在于我们使用大O表示法来表示算法时间复杂度时经常忽略的那个参数“n”的定义。在提到O(n), O(log(n))时,参数n的定义到底是什么?

认为foo3函数的时间复杂度是O(n),这里的n指的是输入的长度。而如果认为foo1的时间复杂度也是O(n),那么这里的n其实指的是输入的值了。而此时输入的长度和值之间,存在2^n的关系。如果强行让n表示为输入的数的二进制长度,那么说foo1函数的时间复杂度是O(2^n)也就不难理解了。

那么,大O表示法下,这个n到底表示什么意思,有一个确切的定义么?

其实是有的,参见维基百科 Time complexity词条:

In computer science, the time complexity of an algorithm quantifies the amount of time taken by an algorithm to run as a function of the length of the string representing the input.

所以,严格来说,n应该取输入的“长度”,而不是输入的“值”。

再次反转

不要怀疑了,大O表示法的n指的就是输入的长度。在此前提下,你是否开始认同foo1的时间复杂度是O(2^n)了?

但是等等,为什么是一定是2的指数级?又没规定所有计算机都必须是二进制表示的。如果是十进制(事实上第一台计算机就是十进制)表示的呢?譬如17,在十进制下,“长度”是2,二进制下,“长度”就成了5。而我们讨论算法的时间复杂度时,谈论的是算法自身的性质,而不会依赖于具体实现,这个函数的时间复杂度为什么不能是O(10^n)

根本原因

造成这个矛盾的根源在于,时间复杂度的定义是输入字符串的长度,但用什么“编码方式”来表示输入却没有指定。

编码不一定指的是字符串编码,7、七、柒、seven、Ⅶ等等都是对抽象数字“7”的一种编码。而算法的输入用不同方式编码表示的长度肯定不同。

极端一点,如果用一进制来编码,4就是1111,那么这种情况下foo1的时间复杂度还真就是O(n)了!

伪多项式复杂度

因为这个问题真是太容易引起歧义了,并且对于foo1函数,明显也是O(n)更符合人的直觉。因此有了这么一个名词:Pseudo-polynomial time(伪多项式时间复杂度)

... Since computational complexity measures difficulty with respect to the length of the (encoded) input, this naive algorithm is actually exponential. It is, however, pseudo-polynomial time.

...
The distinction between the value of a number and its length is one of encoding: if numeric inputs are always encoded in unary, then pseudo-polynomial would coincide with polynomial.

看!真相大白!

学过计算理论的同学应该在最开始就一眼看出了这是一个“伪多项式时间”的问题。如果真正理解这个词,这个问题也就不会让人产生困扰了。

结语

所以,最后,foo1这个“伪多项式时间”算法的复杂度用大O表示法到底是多少?

O(n), O(2^n),都对又都不对,取决于对输入n的编码,如果采用不符合直觉的unary numeral system(一进制),那么得到的就是符合直觉的O(n),而如果采用符合直觉的的二进制或十进制,那么时间复杂度就是指数级。

我们平时张口即来的算法时间复杂度其实是有歧义、不完备的,稍加思考就会发现前文介绍的那种矛盾。所以,在这种情况下,最好还是尊重大部分的人的常识比较好。


本文看似出了一道无意义的题,最终也没有得到一个确切的答案。但在思考的过程中拓展认知的局限,才是真正有意义的事情。

原文地址:https://lxiange.com/posts/time-complexity-ambiguity.html

目录
相关文章
|
1月前
|
机器学习/深度学习 缓存 算法
Python算法设计中的时间复杂度与空间复杂度,你真的理解对了吗?
【10月更文挑战第4天】在Python编程中,算法的设计与优化至关重要,尤其在数据处理、科学计算及机器学习领域。本文探讨了评估算法性能的核心指标——时间复杂度和空间复杂度。通过详细解释两者的概念,并提供快速排序和字符串反转的示例代码,帮助读者深入理解这些概念。同时,文章还讨论了如何在实际应用中平衡时间和空间复杂度,以实现最优性能。
66 6
|
3月前
|
机器学习/深度学习 算法 程序员
读《趣学算法》:重开算法之门,时间复杂度与空间复杂度
本文是作者阅读《趣学算法》后的笔记,介绍了算法复杂度的基本概念,包括时间复杂度和空间复杂度的不同阶表示,并通过具体例子展示了如何计算和理解算法的效率。
68 2
读《趣学算法》:重开算法之门,时间复杂度与空间复杂度
|
1月前
|
搜索推荐 算法
插入排序算法的平均时间复杂度解析
【10月更文挑战第12天】 插入排序是一种简单直观的排序算法,通过不断将未排序元素插入到已排序部分的合适位置来完成排序。其平均时间复杂度为$O(n^2)$,适用于小规模或部分有序的数据。尽管效率不高,但在特定场景下仍具优势。
|
4月前
|
机器学习/深度学习 存储 算法
颠覆认知!Python算法设计中的时间复杂度与空间复杂度,你真的理解对了吗?
【7月更文挑战第22天】在Python算法设计中,时间与空间复杂度是评估算法效能的核心。时间复杂度不仅限于大O表示法,还涵盖平均与最坏情况分析。空间复杂度虽关注额外存储,但也反映内存效率。平衡二者需视场景而定,如利用原地算法减少内存消耗,或牺牲空间加速执行。算法优化技巧,如分治与动态规划,助你在资源与速度间找寻最优解,从而高效应对大数据挑战。
50 3
|
1月前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
26 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
|
1月前
|
存储 算法
算法的时间复杂度和空间复杂度
本文详细讨论了算法的时间复杂度和空间复杂度,包括它们的概念、计算方法和常见复杂度的对比,并通过多个实例解释了如何计算算法的时间和空间复杂度。
74 0
算法的时间复杂度和空间复杂度
|
2月前
|
算法 Python
震惊!Python 算法设计背后,时间复杂度与空间复杂度的惊天秘密大起底!
在 Python 算法设计中,理解并巧妙运用时间复杂度和空间复杂度的知识,是实现高效、优雅代码的必经之路。通过不断地实践和优化,我们能够在这两个因素之间找到最佳的平衡点,创造出性能卓越的程序。
40 4
|
1月前
|
算法 C语言
深入理解算法效率:时间复杂度与空间复杂度
深入理解算法效率:时间复杂度与空间复杂度
|
3月前
|
机器学习/深度学习 存储 算法
算法时间复杂度分析
这篇文章讲解了如何分析算法的时间复杂度,包括关注循环执行次数最多的代码段、总复杂度的确定、嵌套代码复杂度的计算方法,并提供了大O阶的推导步骤和常见时间复杂度的列表,同时还介绍了空间复杂度的概念及其重要性。
|
3月前
|
搜索推荐
九大排序算法时间复杂度、空间复杂度、稳定性
九大排序算法的时间复杂度、空间复杂度和稳定性,提供了对各种排序方法效率和特性的比较分析。
150 1