大多数拥有计算机科学学位的人肯定知道大O代表什么。 它帮助我们衡量一个算法的可扩展性。
但我很好奇,你是如何计算或近似你的算法的复杂性的?
大多数拥有计算机科学学位的人肯定知道大O代表什么。 它帮助我们衡量一个算法的可扩展性。
但我很好奇,你是如何计算或近似你的算法的复杂性的?
当前回答
不要忘记考虑空间的复杂性,如果内存资源有限,这也是一个值得关注的问题。例如,你可能听到有人想要一个常数空间算法,这基本上是说算法所占用的空间量不依赖于代码中的任何因素。
有时,复杂性可能来自于某个东西被调用了多少次,循环执行的频率,内存分配的频率,等等,这是回答这个问题的另一部分。
最后,大O可以用于最坏情况、最佳情况和摊销情况,其中通常用最坏情况来描述算法可能有多糟糕。
其他回答
大O符号很有用,因为它很容易使用,并且隐藏了不必要的复杂性和细节(对于一些不必要的定义)。求解分治算法复杂性的一种好方法是树法。假设你有一个带有中值过程的快速排序版本,所以你每次都将数组分割成完美平衡的子数组。
现在,构建一个与所使用的所有数组对应的树。根结点有原始数组,根结点有两个子数组。重复此步骤,直到底部有单个元素数组。
由于我们可以在O(n)时间内找到中位数,并在O(n)时间内将数组分成两部分,因此在每个节点上所做的功为O(k),其中k是数组的大小。树的每一层都包含(最多)整个数组,所以每层的功是O(n)(子数组的大小加起来是n,因为每层有O(k),我们可以把它加起来)。树中只有log(n)层,因为每次我们将输入减半。
因此,我们可以将功的上限设为O(n*log(n))。
然而,大O隐藏着一些我们有时不能忽视的细节。考虑计算斐波那契数列
a=0;
b=1;
for (i = 0; i <n; i++) {
tmp = b;
b = a + b;
a = tmp;
}
假设a和b在Java中是biginteger或者其他可以处理任意大数字的东西。大多数人会毫不犹豫地说这是一个O(n)算法。理由是,在for循环中有n次迭代,而O(1)工作在循环的一侧。
但是斐波那契数列很大,第n个斐波那契数列是n的指数级,所以仅仅是存储它就需要n个字节。对大整数执行加法将花费O(n)个工作量。所以在这个过程中所做的总功是
一加二加三……+ n = n(n-1)/2 = O(n)
所以这个算法在二次时间内运行!
好问题!
免责声明:这个答案包含虚假陈述,见下面的评论。
如果您正在使用大O,那么您正在谈论的是最坏的情况(后面将详细介绍它的含义)。此外,在平均情况下有大写的theta,在最佳情况下有大的omega。
你可以在这个网站上找到大O的正式定义:https://xlinux.nist.gov/dads/HTML/bigOnotation.html
f(n) = O(g(n))表示存在正常数c和k,使得当n≥k时0≤f(n)≤cg(n)。对于函数f, c和k的值必须是固定的,且不依赖于n。
好的,那么我们所说的"最佳情况"和"最坏情况"是什么意思呢?
这一点可以通过例子得到最清楚的说明。例如,如果我们使用线性搜索在一个排序数组中查找一个数字,那么最坏的情况是我们决定搜索数组的最后一个元素,因为这将花费与数组中有多少项一样多的步骤。最好的情况是当我们搜索第一个元素时,因为我们将在第一次检查之后完成。
The point of all these adjective-case complexities is that we're looking for a way to graph the amount of time a hypothetical program runs to completion in terms of the size of particular variables. However for many algorithms you can argue that there is not a single time for a particular size of input. Notice that this contradicts with the fundamental requirement of a function, any input should have no more than one output. So we come up with multiple functions to describe an algorithm's complexity. Now, even though searching an array of size n may take varying amounts of time depending on what you're looking for in the array and depending proportionally to n, we can create an informative description of the algorithm using best-case, average-case, and worst-case classes.
抱歉,这是如此糟糕的写作和缺乏太多的技术信息。但希望这能让时间复杂度类更容易理解。一旦你熟悉了这些,你就可以很简单地解析你的程序,寻找像for-loops这样依赖于数组大小的东西,并根据你的数据结构推理什么样的输入会导致简单的情况,什么样的输入会导致最坏的情况。
熟悉我使用的算法/数据结构和/或快速分析迭代嵌套。难点在于,当您调用一个库函数时,可能会多次调用—您常常不确定是否在不必要的时候调用了函数,或者它们正在使用什么实现。也许库函数应该有一个复杂度/效率度量,无论是大O还是其他度量,都可以在文档或智能感知中得到。
将算法分解成你知道的大O符号,并通过大O运算符组合。这是我知道的唯一办法。
欲了解更多信息,请查看有关该主题的维基百科页面。
虽然知道如何计算出特定问题的大O时间是有用的,但了解一些一般情况可以在很大程度上帮助您在算法中做出决策。
以下是一些最常见的案例,摘自http://en.wikipedia.org/wiki/Big_O_notation#Orders_of_common_functions:
O(1) -确定一个数字是偶数还是奇数;使用常量大小的查找表或哈希表
O(logn) -用二分搜索在排序数组中查找一个项
O(n) -在未排序的列表中查找一个项;两个n位数相加
O(n2) -用一个简单的算法乘以两个n位数字;添加两个n×n矩阵;冒泡排序或插入排序
O(n3) -用简单的算法乘以两个n×n矩阵
O(cn) -使用动态规划找到旅行商问题的(精确)解;使用蛮力判断两个逻辑语句是否等效
O(n!) -通过暴力搜索解决旅行推销员问题
O(nn) -通常用来代替O(n!)来推导更简单的渐近复杂度公式