问题
如何找到算法的时间复杂度?
在SO上发布问题之前我做了什么?
但是我无法找到关于如何计算时间复杂度的明确而直接的解释。
我知道什么?
说一个像下面那样简单的代码:
char h = 'y'; // This will be executed 1 time
int abc = 0; // This will be executed 1 time
说出如下所示的循环:
for (int i = 0; i < N; i++) {
Console.Write('Hello World !');
}
int i = 0; 这只会执行一次。
实际上,时间计算为i=0
,而不是声明。
i&lt; N; 这将执行 N + 1 次
i ++; 这将执行 N 次
因此该循环所需的操作数为
{1+(N + 1)+ N} = 2N + 2
注意:这仍然可能是错误的,因为我对我对计算时间复杂度的理解没有信心
我想知道什么?
好的,我认为我知道这些小的基本计算,但在大多数情况下,我已经看到了时间的复杂性
O(N),O(n2),O(log n),O(n!) ......和许多other,
任何人都可以帮我理解如何计算算法的时间复杂度?我相信有很多像我这样的新手想知道这件事。
答案 0 :(得分:355)
以下答案是从上方复制的(如果优秀链接破裂)
计算时间复杂度的最常用指标是Big O表示法。这消除了所有常数因子,因此当N接近无穷大时,可以相对于N估计运行时间。一般来说,你可以这样想:
statement;
是不变的。声明的运行时间不会相对于N.
发生变化for ( i = 0; i < N; i++ )
statement;
是线性的。循环的运行时间与N成正比。当N加倍时,运行时间也是如此。
for ( i = 0; i < N; i++ ) {
for ( j = 0; j < N; j++ )
statement;
}
是二次的。两个循环的运行时间与N的平方成正比。当N加倍时,运行时间增加N * N.
while ( low <= high ) {
mid = ( low + high ) / 2;
if ( target < list[mid] )
high = mid - 1;
else if ( target > list[mid] )
low = mid + 1;
else break;
}
是对数的。算法的运行时间与N除以2的次数成正比。这是因为算法在每次迭代时将工作区域分成两半。
void quicksort ( int list[], int left, int right )
{
int pivot = partition ( list, left, right );
quicksort ( list, left, pivot - 1 );
quicksort ( list, pivot + 1, right );
}
N * log(N)。运行时间由N个循环(迭代或递归)组成,这些循环是对数的,因此算法是线性和对数的组合。
一般来说,对一个维度中的每个项目执行某些操作是线性的,对二维中的每个项目执行某些操作是二次的,将工作区域分成两半是对数的。还有其他Big O指标,如立方,指数和平方根,但它们并不常见。 Big O表示法被描述为O(),其中是度量。快速排序算法将被描述为O(N * log(N))。
请注意,这些都没有考虑到最佳,平均和最差情况的衡量标准。每个都有自己的Big O表示法。另请注意,这是一个非常简单的解释。 Big O是最常见的,但它也显示得更复杂。还有其他符号,如大欧米茄,小o和大theta。您可能不会在算法分析课程之外遇到它们。 ;)
答案 1 :(得分:341)
如何找到算法的时间复杂度
根据输入的大小计算它将执行多少个机器指令,然后将表达式简化为最大值(当N非常大时),并且可以包含任何简化常量因子。
例如,让我们看看我们如何简化2N + 2
机器指令,将其描述为O(N)
。
为什么我们删除了两个2
s?
当N变大时,我们对算法的性能感兴趣。
考虑两个术语2N和2.
当N变大时,这两个术语的相对影响是什么?假设N是百万。
然后第一个词是200万,第二个词只有2个。
出于这个原因,我们放弃了除大N之外的所有条款。
所以,现在我们已经从2N + 2
转到2N
。
传统上,我们只关注性能直到常数因素。
这意味着当N很大时,我们并不在乎是否存在性能差异的恒定倍数。无论如何,2N的单位首先没有明确定义。因此,我们可以乘以或除以常数因子来得到最简单的表达式。
因此2N
变为N
。
答案 2 :(得分:136)
从这里采取 - Introduction to Time Complexity of an Algorithm
在计算机科学中,算法的时间复杂度量化了算法作为表示输入的字符串长度的函数运行所花费的时间量。
算法的时间复杂度通常使用大O表示法表示,其不包括系数和低阶项。当以这种方式表达时,时间复杂度被称为渐近地描述,即,随着输入大小变为无穷大。
例如,如果算法对大小为n的所有输入所需的时间最多为5n 3 + 3n,则渐近时间复杂度为O(n 3 )。稍后会详细介绍。
更多例子:
如果算法需要相同的时间量而不考虑输入大小,则称算法在恒定时间内运行。
示例:
如果算法的时间执行与输入大小成正比,则算法以线性时间运行,即随着输入大小的增加,时间会线性增长。
考虑下面的例子,下面我是线性搜索一个元素,它的时间复杂度为O(n)。
int find = 66;
var numbers = new int[] { 33, 435, 36, 37, 43, 45, 66, 656, 2232 };
for (int i = 0; i < numbers.Length - 1; i++)
{
if(find == numbers[i])
{
return;
}
}
更多例子:
如果算法的时间执行与输入大小的对数成正比,则称算法以对数时间运行。
回想一下&#34;二十个问题&#34;游戏 - 任务是猜测间隔中隐藏数字的值。每当你猜测时,你会被告知你的猜测是太高还是太低。二十个问题游戏意味着使用您的猜测数量将间隔大小减半的策略。这是一般问题解决方法的示例,称为二分搜索
如果算法的时间执行与输入大小的平方成正比,则称算法以二次时间运行。
示例:
答案 3 :(得分:86)
虽然这个问题有一些很好的答案。我想在这里给出另一个答案,其中有几个例子loop
。
O(n):如果循环变量递增/递减恒定量,则循环的时间复杂度被视为 O(n)。例如,以下函数具有 O(n)时间复杂度。
// Here c is a positive integer constant
for (int i = 1; i <= n; i += c) {
// some O(1) expressions
}
for (int i = n; i > 0; i -= c) {
// some O(1) expressions
}
O(n ^ c):嵌套循环的时间复杂度等于最内层语句的执行次数。例如,以下示例循环具有 O(n ^ 2)时间复杂度
for (int i = 1; i <=n; i += c) {
for (int j = 1; j <=n; j += c) {
// some O(1) expressions
}
}
for (int i = n; i > 0; i += c) {
for (int j = i+1; j <=n; j += c) {
// some O(1) expressions
}
例如,选择排序和插入排序具有 O(n ^ 2)时间复杂度。
O(Logn)如果循环变量被分割/乘以常量,则循环的复杂度被视为 O(Logn)。 / p>
for (int i = 1; i <=n; i *= c) {
// some O(1) expressions
}
for (int i = n; i > 0; i /= c) {
// some O(1) expressions
}
例如二进制搜索具有 O(Logn)时间复杂度。
O(LogLogn)如果循环变量以指数方式减少/增加一个恒定量,则循环的时间复杂度被视为 O(LogLogn)。
// Here c is a constant greater than 1
for (int i = 2; i <=n; i = pow(i, c)) {
// some O(1) expressions
}
//Here fun is sqrt or cuberoot or any other constant root
for (int i = n; i > 0; i = fun(i)) {
// some O(1) expressions
}
时间复杂度分析的一个例子
int fun(int n)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j < n; j += i)
{
// Some O(1) task
}
}
}
<强>分析强>:
For i = 1, the inner loop is executed n times.
For i = 2, the inner loop is executed approximately n/2 times.
For i = 3, the inner loop is executed approximately n/3 times.
For i = 4, the inner loop is executed approximately n/4 times.
…………………………………………………….
For i = n, the inner loop is executed approximately n/n times.
因此上述算法的总时间复杂度为(n + n/2 + n/3 + … + n/n)
,后者变为n * (1/1 + 1/2 + 1/3 + … + 1/n)
关于系列(1/1 + 1/2 + 1/3 + … + 1/n)
的重要事项等于 O(Logn)。因此,上述代码的时间复杂度为 O(nLogn)。
答案 4 :(得分:67)
1 - 基本操作(算术,比较,访问数组元素,赋值):运行时间始终为常数O(1)
示例:
read(x) // O(1)
a = 10; // O(1)
a = 1.000.000.000.000.000.000 // O(1)
2 - 如果是then else语句:仅从两个或多个可能的语句中获取最大运行时间。
示例:
age = read(x) // (1+1) = 2
if age < 17 then begin // 1
status = "Not allowed!"; // 1
end else begin
status = "Welcome! Please come in"; // 1
visitors = visitors + 1; // 1+1 = 2
end;
所以,上面伪代码的复杂度是T(n)= 2 + 1 + max(1,1 + 2)= 6.因此,它的大哦仍然是常数T(n)= O(1)
3 - 循环(for,while,repeat):此语句的运行时间是循环次数乘以循环内的操作数。
示例:
total = 0; // 1
for i = 1 to n do begin // (1+1)*n = 2n
total = total + i; // (1+1)*n = 2n
end;
writeln(total); // 1
因此,其复杂度为T(n)= 1 + 4n + 1 = 4n + 2.因此,T(n)= O(n)。
4 - 嵌套循环(在循环内循环):由于主循环内至少有一个循环,因此该语句的运行时间使用O(n ^ 2)或O(n ^ 3)。
示例:
for i = 1 to n do begin // (1+1)*n = 2n
for j = 1 to n do begin // (1+1)n*n = 2n^2
x = x + 1; // (1+1)n*n = 2n^2
print(x); // (n*n) = n^2
end;
end;
分析算法时有一些常见的运行时间:
O(1) - 恒定时间 恒定时间意味着运行时间是恒定的,它不受输入大小的影响。
O(n) - 线性时间 当算法接受n个输入大小时,它也会执行n个操作。
O(log n) - 对数时间 运行时间为O(log n)的算法比O(n)略快。通常,算法将问题划分为具有相同大小的子问题。示例:二进制搜索算法,二进制转换算法。
O(n log n) - 线性时间 这种运行时间通常在“分而治之算法”中找到,它将问题递归地分成子问题,然后在n时间内合并它们。示例:合并排序算法。
O(n 2 ) - 二次时间 Look Bubble Sort算法!
O(n 3 ) - 立方时间 它与O(n 2 )具有相同的原理。
O(2 n ) - 指数时间 输入变大时非常慢,如果n = 1000.000,T(n)将是21000.000。 Brute Force算法有这个运行时间。
O(n!) - 因子时间 最快!!!示例:旅行商问题(TSP)
取自this article。很好解释应该给读一个。
答案 5 :(得分:39)
当您分析代码时,您必须逐行分析,计算每个操作/识别时间复杂度,最后,您必须将其求和以获得全局。
例如,你可以有一个带有线性复杂度的简单循环,但是后来在同一个程序中你可以有一个具有立方复杂度的三重循环,所以你的程序将具有立方复杂度。增长的功能顺序就在这里发挥作用。
让我们看一下算法时间复杂度的可能性,你可以看到我上面提到的增长顺序:
常量时间 的增长顺序为1
,例如:a = b + c
。
对数时间 的增长顺序为LogN
,通常会发生
当你将某些东西分成两半时(二元搜索,树木,甚至循环),或以相同方式乘以某些东西。
线性 ,增长顺序为N
,例如
int p = 0;
for (int i = 1; i < N; i++)
p = p + 2;
Linearithmic ,增长的顺序是n*logN
,通常发生在分而治之的算法中。
Cubic ,增长顺序N^3
,经典示例是一个三重循环,您可以检查所有三元组:
int x = 0;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
for (int k = 0; k < N; k++)
x = x + 2
指数 ,增长顺序2^N
,通常在您进行详尽搜索时发生,例如检查某些集合的子集。
答案 6 :(得分:31)
总的来说,时间复杂度是一种总结算法运算次数或运行时间随着输入大小的增加而增长的方式。
与生活中的大多数事情一样,鸡尾酒会可以帮助我们理解。
<强> O(N)强>
当你到达聚会时,你必须握住每个人的手(对每个项目进行操作)。随着与会者人数N
的增加,您动摇每个人的时间/工作时间将增加为O(N)
。
为什么O(N)
而不是cN
?
与人握手所需的时间有所不同。您可以将其平均并以常量c
捕获它。但是这里的基本操作---与每个人握手 - 无论O(N)
是什么,都始终与c
成正比。在辩论我们是否应该参加鸡尾酒会时,我们常常更感兴趣的是,我们必须满足每个人,而不是那些会议的细节。
<强> O(N ^ 2)强>
鸡尾酒会的主持人希望你玩一个愚蠢的游戏,每个人都会遇到其他人。因此,您必须与N-1
其他人见面,因为下一个人已经遇到过您,他们必须会见N-2
人,依此类推。这个系列的总和是x^2/2+x/2
。随着与会者人数的增加,x^2
词语变得很快快,所以我们放弃其他所有内容。
<强> O(N ^ 3)强>
您必须与其他所有人见面,并且在每次会议期间,您必须谈论会议室中的其他人。
<强> O(1)强>
主持人想宣布一些事情。他们敲着酒杯,大声说话。每个人都听到了他们。事实证明,无论有多少与会者,此操作总是花费相同的时间。
O(日志N)
主持人按字母顺序排列在桌旁。丹在哪里?你的理由是他必须介于亚当和曼迪之间(当然不是曼迪和扎克之间!)。鉴于此,他是乔治和曼迪之间吗?不,他必须介于亚当和弗雷德之间,以及辛迪和弗雷德之间。等等...我们可以通过查看该组的一半然后是该组的一半来有效地定位Dan。最后,我们会关注 O(log_2 N)个人。
O(N log N)
您可以使用上述算法找到坐在桌旁的位置。如果很多人一次一个地来到桌子上,并且所有人都这样做了,那将需要 O(N log N)时间。事实证明,必须比较任何项目集合所需的时间。
最佳/最差情况
你到达派对并需要找到Inigo - 需要多长时间?这取决于你什么时候到达。如果每个人都在四处奔波,那你就遇到了最糟糕的情况:这需要花费O(N)
时间。但是,如果每个人坐在桌旁,只需O(log N)
次。或者也许你可以利用主持人的酒杯喊叫力,它只需要O(1)
时间。
假设主机不可用,我们可以说Inigo查找算法的下限为O(log N)
,上限为O(N)
,具体取决于当事人的状态到达。
Space&amp;通信强>
同样的想法可以应用于理解算法如何使用空间或通信。
Knuth写了一篇关于前者的好文章,名为"The Complexity of Songs"。
定理2:存在复杂度为O(1)的任意长歌。
证明:(由于凯西和阳光乐队)。考虑由(15)定义的歌曲Sk,但是用
V_k = 'That's the way,' U 'I like it, ' U
U = 'uh huh,' 'uh huh'
表示所有k。
答案 7 :(得分:4)
我知道这个问题可以追溯到这里,并且在这里有一些很好的答案,但是我想为那些在这篇文章中偶然发现的具有数学意识的人分享另一点。在研究复杂性时,Master theorem是另一个有用的东西。我没有在其他答案中看到它。
答案 8 :(得分:2)
O(n)是用于编写算法的时间复杂度的大O符号。当你在算法中加起执行次数时,你会得到一个像2N + 2这样的结果的表达式,在这个表达式中,N是主导的术语(如果它的值增加或减少,则对表达具有最大影响的术语)。现在O(N)是时间复杂性,而N是主导术语。 实施例
For i= 1 to n;
j= 0;
while(j<=n);
j=j+1;
此处内循环的执行总数为n + 1,外循环的执行总数为n(n + 1)/ 2,因此整个算法的执行总数为n + 1 + n(n + 1/2)=(n ^ 2 + 3n)/ 2。 这里n ^ 2是主导项,因此该算法的时间复杂度为O(n ^ 2)