所以我有一个关于如何验证函数的大O的快速问题。
例如:对5000000元素数组进行排序的快速排序算法产生0.008524秒的时间间隔,运行相同的算法,1000000个元素,产生0.017909。如果我的快速排序是不是n * log(n)的大O,我将如何检查大O
我认为我理解:n增加2因此运行时间应增加2 * log(2)?
f(n)= 0.008524 - > n log(n)
f(2n)= 0.017909 - > 2n * log(2n)
答案 0 :(得分:2)
Big-O表示法是渐近的。这意味着它只适用于极限,因为n变大。
使用50和100个元素进行排序可能无法跟踪O(n log n)的原因有很多,缓存效果可能是候选者。如果你尝试100,000比200,000而不是100万,你可能会发现它跟踪得更好。
另一种可能性是大多数快速排序实现平均只有O(n log n);一些投入需要更长时间。 50个元素遇到这种病理输入的几率高于100,000个。
但最终,你并没有“验证”大O运行时间;你根据算法的作用证明它。
答案 1 :(得分:1)
Big-O表示法通常不关心运行时间。它关注算法执行的操作数。建立它的唯一方法是查看函数的代码。
运行时不仅会受到低阶项的影响(请记住,大O符号只关注最高阶项),还有程序启动开销等问题。缓存效率和分支预测。
尽管如此,在你的情况下,这些其他影响都不可能是重要的。在这种情况下,如果 n 加倍,那么您可以预期运行时间从 knlog(n)增加到 k.2n.log(2n) )= k(2n.log(2)+ 2n.log(n))。
答案 2 :(得分:1)
要记住以下几点:首先,当您更改尺寸时,硬件(至少在典型的计算机上)也会产生影响。特别是,当您的数据变得很大以适应特定大小的缓存时,您可能会看到运行时间的大幅跳跃,这完全独立于所讨论的算法。
为了正确理解算法,您应该(可能)首先将某些算法与真正明显的复杂度进行比较,但使用相同大小的数据。一个明显的可能性是,用随机数填充数组需要多长时间。至少假设一个相当典型的PRNG,那当然应该是线性的。
然后计算算法,看看它如何将 relative 更改为相同大小的线性算法。例如,您可以使用以下代码:
#include <vector>
#include <algorithm>
#include <iostream>
#include <time.h>
#include <string>
#include <iomanip>
class timer {
clock_t begin;
std::ostream &os;
std::string d;
public:
timer(std::string const &delim = "\n", std::ostream &rep=std::cout)
: os(rep), begin(clock()), d(delim)
{}
~timer() { os << double(clock()-begin)/CLOCKS_PER_SEC << d; }
};
int main() {
static const unsigned int meg = 1024 * 1024;
std::cout << std::setw(10) << "Size" << "\tfill\tsort\n";
for (unsigned size=10000; size <512*meg; size *= 2) {
std::vector<int> numbers(size);
std::cout << std::setw(10) << size << "\t";
{
timer fill_time("\t");
std::fill_n(numbers.begin(), size, 0);
for (int i=0; i<size; i++)
numbers[i] = rand();
}
{
timer sort_time;
std::sort(numbers.begin(), numbers.end());
}
}
return 0;
}
如果我同时绘制填充时间和排序时间,我会得到类似的结果:
由于我们的尺寸是指数,我们的线性算法显示(粗略)指数曲线。排序的时间显然也在增长(有些)。
编辑:公平地说,我应该补充一点,log(N)的增长速度非常慢,几乎任何实际数据量的数据都很少。对于大多数实际用途,您可以将quicksort(例如)视为大小上的线性,只是比填充内存稍微更大的常数因子。线性增长并绘制结果图表使这一点更加明显:
如果仔细观察,您可能会看到上面一行显示“log(N)”因子中的轻微向上曲线。另一方面,如果我还不知道应该那里,我不确定我是否注意到任何曲率。
答案 3 :(得分:0)
两个数据点还不够。
但是,2 * n * log(2 * n)= 2 * n *(log(n)+ log(2)),所以你可以看到乘数大约应该是2 * log(2)大小加倍。这看起来似乎有点你给的数字。您应该再添加几个点并仔细检查。
请注意,您的计时可能会有一些不变的术语,至少如果您在那里包含程序启动 - 它可能很重要。另一方面,如果您只在没有启动的情况下计算排序阶段,则更准确。您应该对输入数据的许多排列重复此过程,以确保您获得一组有代表性的值。
答案 4 :(得分:0)
使用这个代码,它取决于该方法在100个元素之间运行所需的时间,而不是时间本身。就像我在迭代一个数组一样,这将是线性时间O(n),因为数组必须遍历每个索引到最后。 N表示阵列的大小。此外,从长远来看,Big O符号用于整体方案。如果平均50,100,1000,100000,1000000的时间,你会看到平均值它会有一个O(nlog(n))。