通过计算步骤来计算算法复杂度
Calculating algorithm complexity by counting steps
试图通过计算步骤来计算函数的大0。我想这些就是如何按照例子中的方法计算每一步,但不确定如何计算总数。
int function (int n){
int count = 0; // 1 step
for (int i = 0; i <= n; i++) // 1 + 1 + n * (2 steps)
for (int j = 0; j < n; j++) // 1 + 1 + n * (2 steps)
for (int k = 0; k < n; k++) // 1 + 1 + n * (2 steps)
for (int m = 0; m <= n; m++) // 1 + 1 + n * (2 steps)
count++; // 1 step
return count; // 1 step
}
我想说这个函数是O(n^2),但我不明白它是怎么计算出来的。
例子我一直在看
int func1 (int n){
int sum = 0; // 1 step
for (int i = 0; i <= n; i++) // 1 + 1 + n * (2 steps)
sum += i; // 1 step
return sum; // 1 step
} //total steps: 4 + 3n
和
int func2 (int n){
int sum = 0; // 1 step
for (int i = 0; i <= n; i++) // 1 + 1 + n * (2 steps)
for (int j = 0; j <= n; j++) // 1 + 1 + n * (2 steps)
sum ++; // 1 step
for (int k = 0; k <= n; k++) // 1 + 1 + n * (2 steps)
sum--; // 1 step
return sum; // 1 step
}
//total steps: 3n^2 + 7n + 6
你刚才提出的是非常简单的例子。在我看来,为了理解你的例子,你只需要了解循环的复杂性是如何工作的。
简而言之 (非常简单的)一个循环必须考虑如下的渐近复杂度:
loop (condition) :
// loop body
end loop
- 循环的
condition
应该告诉你循环将执行的次数与输入的大小相比。 - 主体的复杂性(您可以将主体视为子函数并计算复杂性)必须乘以循环的复杂性。
原因很直观:在条件被验证之前,你在代码体中的代码将被重复执行,这是循环(以及代码体)将被执行的次数。
举个例子:
// Array linear assignment
std::vector<int> array(SIZE_ARRAY);
for (int i = 0; i < SIZE_ARRAY; ++i) {
array[i] = i;
}
让我们分析一下这个简单的循环:
首先,我们需要选择输入相对于来计算我们的复杂度函数。这种情况非常简单:变量是数组的大小。这是因为我们想知道当输入数组的大小增加时,程序是如何反应的。
循环将重复
SIZE_ARRAY
次。因此,执行主体的次数是SIZE_ARRAY
次(注意:该值是可变的,不是恒定值)。现在考虑循环体。指令
array[i] = i
不依赖于数组的大小。它需要未知数量的CPU周期,但这个数字总是相同的,即常数
总的来说,我们重复SIZE_ARRAY
次,这条指令占用恒定数量的CPU时钟(假设k
是该值,是恒定的)。
因此,从数学上讲,为这个简单的程序执行的CPU时钟的数量将是SIZE_ARRAY * k
。
用O大符号可以描述极限行为。这是当自变量趋于无穷时函数的行为
我们可以这样写:
O(SIZE_ARRAY * k) = O(SIZE_ARRAY)
这是因为k
是一个常数值,并且根据大0符号的定义该常数不会在无穷远处增长(永远是常数)。
如果我们将SIZE_ARRAY
称为N
(输入的大小),我们可以说我们的函数在时间复杂度上是O(N)
。
最后一个("更复杂")例子:
for (int i = 0; i < SIZE_ARRAY; ++i) {
for (int j = 0; j < SIZE_ARRAY; ++j) {
array[j] += j;
}
}
和之前一样,我们将问题大小与SIZE_ARRAY
进行比较。
- 第一个周期将执行
SIZE_ARRAY
次,即O(SIZE_ARRAY)
。 - 第二个周期执行
SIZE_ARRAY
次。 - 第二个周期的主体是一个指令,它将占用一个常数的CPU周期,假设这个数字是
k
。
用第一个循环执行的时间乘以循环体的复杂度。
O(SIZE_ARRAY) * [first_loop_body_complexity].
但是第一个循环的主体是:
for (int j = 0; j < SIZE_ARRAY; ++j) {
array[j] += j;
}
和前面的例子一样是一个单循环,我们刚刚计算了它的复杂度。它是O(SIZE_ARRAY)
。我们可以看到:
[first_loop_body_complexity] = O(SIZE_ARRAY)
最后,我们的整个复杂度是:
O(SIZE_ARRAY) * O(SIZE_ARRAY) = O(SIZE_ARRAY * SIZE_ARRAY)
O(SIZE_ARRAY^2)
使用N
代替SIZE_ARRAY
.
O(N^2)
免责声明:这不是一个数学解释。这是一个简化的版本,我认为它可以帮助那些刚接触复杂世界的人,就像我第一次接触这个概念时一样毫无头绪。我也不会给你答案。试着帮你到达那里。
这个故事的寓意是:不要数台阶。复杂度不是指执行了多少条指令(我将用这个来代替"步骤")。这本身(几乎)完全无关紧要。用外行的话来说(时间),复杂性是关于执行时间如何随着输入的增长而增长——这就是我最终对复杂性的理解。
让我们一步一步地了解一些最常见的复杂性:
常数复杂度:0 (1)
表示一个算法的执行时间不依赖于输入。执行时间不会随着输入的增加而增加。
例如:auto foo_o1(int n) {
instr 1;
instr 2;
instr 3;
if (n > 20) {
instr 4;
instr 5;
instr 6;
}
instr 7;
instr 8;
};
该函数的执行时间与n
的值无关。注意我是怎么说的,即使一些指令执行与否取决于n
的值。数学上这是因为O(constant) == O(1)
。直观地说,这是因为指令数量的增长与n
不成比例。同样,如果函数有10条instr或1k条指令,则无关紧要。它仍然是O(1)
-恒定的复杂性。
表示执行时间与输入成正比的算法。当给定一个小的输入时,它需要一定的量。当增加输入时,执行时间成比例地增长:
auto foo1_on(int n)
{
for (i = 0; i < n; ++i)
instr;
}
此函数为O(n)
。这意味着当输入加倍时,执行时间会增加一倍。这对任何输入都成立。例如,当你将输入从10翻倍到20时,当你将输入从1000翻倍到2000时,算法执行时间的增长因素或多或少是相同的。
与忽略相对于对"最快"增长贡献不大的想法一致,所有接下来的函数仍然具有O(n)复杂度。数学上O
的复杂度是上界的。这导致O(c1*n + c0) = O(n)
auto foo2_on(int n)
{
for (i = 0; i < n / 2; ++i)
instr;
}
此处:O(n / 2) = O(n)
auto foo3_on(int n)
{
for (i = 0; i < n; ++i)
instr 1;
for (i = 0; i < n; ++i)
instr 2;
}
这里O(n) + O(n) = O(2*n) = O(n)
多项式二阶复杂度:O(n^2)
这告诉你,随着输入的增加,执行时间会越来越长。例如,下一个是O(n^2)
算法的有效行为:
Read:当你从…对. .执行时间可以增加…*
- 从100到200:1.5倍
- 从200到400:1.8倍
- 从400到800:2.2倍
- 从800到1600:6倍
- 从1600到3200:500倍
试试这个! 。编写一个O(n^2)
算法。输入加倍。首先,您将看到计算时间的小幅增加。有一次,它只是吹,你必须等几分钟,而在前面的步骤中,它只需要几秒钟。
这很容易理解,只要你看一下n^2
图。
auto foo_on2(int n)
{
for (i = 0; i < n; ++i)
for (j = 0; j < n; ++j)
instr;
}
这个函数是如何O(n)
?简单:第一个循环执行n
次。(我不在乎是n times plus 3
还是4*n
。然后,对于第一个循环的每一步,第二个循环执行n
次。i循环有n
次迭代。对于每个i迭代,有n
j迭代。所以我们总共有 n * n = n^2
j次迭代。因此,O(n^2)
还有其他有趣的复杂性,如对数,指数等。一旦你理解了数学背后的概念,它就会变得非常有趣。例如,对数复杂度O(log(n))
的执行时间随着输入的增长变慢和变慢。当你查看日志图时,你可以清楚地看到这一点。
网上有很多关于复杂性的资源。搜索。阅读。不明白!再次搜索。阅读。拿纸和笔。理解!重复。
保持简单:
O(N)表示小于或等于到N。因此,在一个代码片段中,我们忽略所有的,并专注于执行步骤最多(最高功率)的代码来解决问题/完成执行。
跟随你的例子:
int function (int n){
int count = 0; // ignore
for (int i = 0; i <= n; i++) // This looks interesting
for (int j = 0; j < n; j++) // This looks interesting
for (int k = 0; k < n; k++) // This looks interesting
for (int m = 0; m <= n; m++) // This looks interesting
count++; // This is what we are looking for.
return count; // ignore
}
语句要完成,我们需要"wait"或"cover"或"step" (n + 1) * n * n * (n + 1) => O(~ n ^4)。
第二个例子:
int func1 (int n){
int sum = 0; // ignore
for (int i = 0; i <= n; i++) // This looks interesting
sum += i; // This is what we are looking for.
return sum; // ignore
}
需要n + 1个步骤=> 0 (~n)。
第三个例子:
int func2 (int n){
int sum = 0; // ignore
for (int i = 0; i <= n; i++) // This looks interesting
for (int j = 0; j <= n; j++) // This looks interesting
sum ++; // This is what we are looking for.
for (int k = 0; k <= n; k++) // ignore
sum--; // ignore
return sum; // ignore
}
我们需要(n + 1) * (n + 1)步=> 0 (~ n ^2)
在这些简单的情况下,您可以通过查找最常执行的指令来确定时间复杂度,然后找出该数字如何依赖于n
。
例1中,count++
执行n^4次=> 0 (n^4)
例2中,sum += i;
执行n次=> 0 (n)
在例3中,sum ++;
执行n^2次=> 0 (n^2)
实际上,这是不正确的,因为有些循环执行了n+1次,但这并不重要。在例1中,该指令实际执行了(n+1)^2*n^2次,这与n^4 + 2 n^3 + n^2相同。对于时间复杂度,只计算最大的功率
- 如何实现高效的算法来计算大型数据集的多个不同值?
- OpenCV - Python 断言错误:SAD 算法 - 立体相机视差图计算
- 通过指针算法计算数组长度
- 计算数组重复次数的组合的有效算法,加起来达到给定的总和
- 我们如何并行运行算法的 n 个实例并以有效的方式计算结果函数的平均值?
- 当比特数不是8的倍数时,使用切片8算法计算CRC
- 使用 Prim 算法计算最小生成树:如何使其简单?
- 计算数组中存在其总和的对数的算法
- 如何计算摘要/将使用哪种算法?
- 如何在不导致堆栈溢出的情况下计算非常大的数字和很小的 HCF.我正在使用欧几里得算法
- 如何通过 stl 容器和算法库计算两个向量的内积?
- 使用加法链接计算 (a^x)mod n.C++算法
- 通过分而治算法计算数组的最大数量
- 浮点计算可以用于任何可靠的函数,特别是容器和算法吗?
- OPENCV:如何使用5点算法从来自不同相机的两个图像之间的特征匹配来计算必需矩阵
- MFC 用于计算控件的高光、阴影等的算法或函数是什么?
- 使用位移算法计算平方根始终输出相同的数字
- 线程:如何在C或C++中精确计算算法的执行时间(函数的持续时间)
- 尝试计算算法的运行时间
- 通过计算步骤来计算算法复杂度