如何将简单的计算机算法转换为数学函数以确定大 o 符号?

Posted

技术标签:

【中文标题】如何将简单的计算机算法转换为数学函数以确定大 o 符号?【英文标题】:How to convert a simple computer algorithm into a mathematical function in order to determine the big o notation? 【发布时间】:2016-05-18 02:57:51 【问题描述】:

在我的大学里,我们正在学习大 O 表示法。但是,根据大 o 表示法,我有一个问题是,如何将简单的计算机算法(例如线性搜索算法)转换为数学函数,例如 2n^2 + 1?

这是我用 c++11 编写的一个简单且非鲁棒的线性搜索算法。注意:为了简单起见,我忽略了所有头文件(iostream)和函数参数。我将只使用基本的运算符、循环和数据类型来展示算法。

int array[5] = 1,2,3,4,5;
// Variable to hold the value we are searching for
int searchValue;
// Ask the user to enter a search value
cout << "Enter a search value: ";
cin >> searchValue;
// Create a loop to traverse through each element of the array and find
// the search value
for (int i = 0; i < 5; i++)

if (searchValue == array[i])

cout << "Search Value Found!" << endl;

else
// If S.V. not found then print out a message
cout << "Sorry... Search Value not found" << endl;

最后,您如何将算法转换为数学函数,以便我们可以使用大 o 表示法分析算法的实际效率?感谢世界。

【问题讨论】:

我认为您不能自动执行此操作。您必须考虑 N(数组的长度)和运行时(程序中的原始步骤数)之间的总体关系是什么。 至少,为了可读性,不要忽视缩进的好处。 谢谢大家的建议。 猜猜为什么线性搜索被称为“线性”。 通常测量的是操作和内存访问。在循环中,将 i 的值与数组的大小进行比较 5 次并递增 5 次,并检索 5 次以用作数组索引。该数组被访问 5 次,并与搜索值比较 5 次。那么请注意每个操作和内存访问是如何进行 5 次的?嗯,5 是数组中的项目数,所以 n=5,数学函数是 Kn(其中 K 是完成 5 次的事情的数量),所以复杂度是 O(n),因为 K 是固定的常数操作数和访问。 【参考方案1】:

首先,请注意,并非总是可以分析算法的时间复杂度,有些我们不知道它们的复杂度,因此我们必须依赖实验数据。

所有方法都意味着计算完成的操作数。所以首先,我们必须定义基本操作的成本,如分配、内存分配、控制结构(if、else、for、...)。我将使用的一些值(使用不同的模型可以提供不同的值):

分配需要固定时间(例如:int i = 0;) 基本操作需要固定时间 (+ - * ∕) 内存分配与分配的内存成正比:分配 n 个元素的数组需要线性时间。 条件需要固定时间(ifelseelse if) 循环所花费的时间与代码运行的次数成正比。

基本分析

一段代码的基本分析是:统计每一行的操作次数。把这些成本加起来。完成。

int i = 1;
i = i*2;
System.out.println(i);

为此,第 1 行有一个操作,第 2 行有一个操作,第 3 行有一个操作。这些操作是恒定的:这是 O(1)。

for(int i = 0; i < N; i++) 
    System.out.println(i);

对于循环,计算循环内的操作次数并乘以循环运行的次数。内部有一项操作需要一定的时间。这运行了 n 次 -> 复杂度为 n * 1 -> O(n)。

for (int i = 0; i < N; i++) 
    for (int j = i; j < N; j++) 
        System.out.println(i+j);
    

这个比较棘手,因为第二个循环基于i 开始迭代。第 3 行执行 2 次操作(加法 + 打印),需要固定时间,因此需要固定时间。现在,运行第 3 行的时间取决于i 的值。列举案例:

当 i = 0 时,j 从 0 变为 N,因此第 3 行运行 N 次。 当 i = 1 时,j 从 1 变为 N,因此第 3 行运行 N-1 次。 ...

现在,总结所有这些,我们必须评估N + N-1 + N-2 + ... + 2 + 1。和的结果是N*(N+1)/2,它是二次的,所以复杂度是O(n^2)。

这就是它在许多情况下的工作原理:计算操作的数量,将所有操作相加,得到结果。

摊销时间

复杂性理论中的一个重要概念是摊销时间。举个例子:运行operation() n次:

for (int i = 0; i < N; i++) 
    operation();

如果有人说operation 需要摊销常数时间,这意味着运行 n 次操作需要线性时间,即使一个特定操作可能需要线性时间。

假设您有一个包含 1000 个元素的空数组。现在,在其中插入 1000 个元素。像馅饼一样容易,每次插入都需要固定的时间。现在,插入另一个元素。为此,您必须创建一个新数组(更大),将旧数组中的数据复制到新数组中,然后插入元素 1001。第 1000 次插入花费恒定时间,最后一次插入花费线性时间。在这种情况下,我们说所有插入都需要摊销的常数时间,因为最后一次插入的成本被其他插入的成本摊销了。

做出假设

在其他一些情况下,获取操作数需要做出假设。一个完美的例子是插入排序,因为它很简单,而且它的运行时间取决于数据的排序方式。

首先,我们必须做出更多假设。排序涉及两个基本操作,即比较两个元素和交换两个元素。在这里,我将考虑它们都花费恒定的时间。这是我们要对数组 a 进行排序的算法:

for (int i = 0; i < a.length; i++) 
    int j = i;
    while (j > 0 && a[j] < a[j-1]) 
        swap(a, i, j);
        j--;
    

第一个循环很简单。不管里面发生什么,它都会运行n次。所以算法的运行时间至少是线性的。现在,为了评估第二个循环,我们必须对数组的排序方式做出假设。通常,我们会尝试定义最佳情况、最坏情况和平均情况的运行时间。

最佳情况:我们从不进入while 循环。这可能吗 ?是的。如果a 是一个排序数组,那么a[j] &gt; a[j-1] 不管j 是什么。因此,我们永远不会进入第二个循环。因此,在这种情况下,执行的操作是第 2 行的赋值和第 3 行的条件评估。两者都需要恒定的时间。由于第一个循环,这些操作运行n 次。那么在最好的情况下,插入排序是线性的。

最坏情况:只有在到达数组开头时才离开 while 循环。也就是说,对于数组中的每个元素,我们将每个元素一直交换到 0 索引。它对应于以相反顺序排序的数组。在这种情况下,我们最终将第一个元素交换 0 次,元素 2 交换 1 次,元素 3 交换 2 次,依此类推,直到元素 n 交换 n-1 次。我们已经知道了这个结果:最坏情况下的插入是二次的。

平均情况:对于平均情况,我们假设项目在数组内随机分布。如果你对数学感兴趣,它涉及概率,你可以在很多地方找到证明。结果是二次的。

结论

这些是分析算法时间复杂度的基础知识。这些案例很简单,但有些算法并不那么好。例如,您可以查看更复杂的配对堆数据结构的复杂性。

【讨论】:

以上是关于如何将简单的计算机算法转换为数学函数以确定大 o 符号?的主要内容,如果未能解决你的问题,请参考以下文章

java 中字符串能不能直接转换为数学表达式进行计算,不可以要怎么转换,求具体代码。简单点,最好带括号

将字符串转换为数学评估 [关闭]

从0到1学算法大O表示法

前端学算法之算法复杂度

大O表示计算

将字符串转换为数学方程[重复]