关于算法的时间复杂度(度量算法执行时间的两种方法、渐进时间复杂度、时间复杂度的几个性质、渐进估算、常见的渐进时间复杂度排序)

目录

度量算法执行时间的两种方法

事后统计法(Post Hoc Analysis):

事前统计法(Pre Hoc Analysis):

渐进时间复杂度

时间复杂度的几个性质

渐进估算

常见的渐进时间复杂度排序


度量算法执行时间的两种方法

算法的时间复杂度一般是指程序运行从开始到结束所需的时间。

算法执行时间需通过依据该算法编制的程序在计算机上运行所消耗的时间来度量,而度量算法执行时间通常有两种方法:事后统计法和事前估算法。

事后统计法是将算法实现后,计算其时间和空间开销,从而确定算法的效率。然而时间和空间开销的计算与计算机软硬件环境相关。同一个算法在不同的机器上执行所花的时间不同,可见这种方法存在明显缺陷,因此我们一般采用事前估算法来评估算法的效率。

事后统计法和事前统计法各自具有的一些优点和缺点:

事后统计法(Post Hoc Analysis):

**优点**:

1. 实用性:事后统计法适用于已经发生的事件和数据收集,因此在一些情况下,它是唯一可行的方法。

2. 数据充分:在使用事后统计法时,我们通常可以利用完整的数据集,包括详细的历史数据,以进行深入的分析。

3. 具体问题解决:可以使用事后统计法来解决具体的问题,如异常事件的分析、错误的原因追溯等。

**缺点**:

1. 反应性:事后统计法只能应用于已经发生的事件,也就是说我们必须先执行相关程序,因此我们无法预测未来事件,只能根据过去的经验做出决策。

2. 无法控制变量:在事后统计法中,我们不能直接控制或操纵变量,因此无法进行实验性的研究。

3. 样本偏差:根据已经发生的事件进行统计分析可能会受到样本选择偏差的影响,因此得出的结论可能不太可靠。

事前统计法(Pre Hoc Analysis):

**优点**:

1. 预测性:事前统计法允许我们在事件发生前进行分析和预测,从而可以采取预防措施或制定未来计划。

2. 控制变量:我们可以在事前统计法中控制和操纵变量,以便进行实验性的研究和测试不同的假设。

3. 减少偏见:通过在研究设计中考虑随机抽样和双盲试验等技术,我们可以减少样本选择偏见和实验者偏见。

**缺点**:

1. 不适用于所有情况:事前统计法需要提前计划和准备,因此可能不适用于某些突发事件或紧急情况。

2. 数据不足:在事前统计法中,我们可能没有足够的数据来支持分析或决策,特别是对于新兴领域或少见事件。

3. 成本高昂:事前统计法可能需要大量资源和时间来收集数据和进行实验,因此成本较高。

综上所述,事后统计法和事前统计法各有其适用的情况和限制。在决定使用哪种方法时,通常需要考虑问题的性质、可用数据、时间和资源等因素。在某些情况下,两种方法可以结合使用,以获得更全面的分析和决策支持。

渐进时间复杂度

要理解清楚渐进时间复杂度我们需要先了解几个概念。

首先,什么是问题规模

你先思考一下有哪些会影响算法执行时间的因素?

我给你列出了几个(这里排序确实与重要程度有关):

1、算法选用的策略的优劣:

使用高效的算法策略可以显著减少执行时间。比如,一个具有较低时间复杂度的算法通常比一个时间复杂度较高的算法执行得更快。在某些情况下,选择最佳算法可能涉及到更多的内存使用,这可能会导致额外的计算时间来读取和写入内存。

2、问题规模,实例特征:

较小的问题规模通常意味着执行时间较短,因为算法需要处理的数据量更少。如果问题规模非常大,执行时间可能会显著增加,因为算法必须处理更多的数据。

算法的时间复杂度不仅与问题题规模相关,实例特征(如数据分布)也可以影响算法的执行时间。例如,在包含n个元素的数组中找给给定元素x,设算法从左向右搜索,如果待搜索的元素x正好是第一个元素,则所需的查找时间最短,这就是算法的最好情况;如果待搜索的元素x是最后一个元素或不在数组中,则是算法的最坏情况;如果需要多次在数组中查找概率检验每个数组元素,则是算法时间开销的平均情况

3、使用的程序语言:

某些编程语言(如C++、Rust)具有更高的执行效率,因此可以使算法运行得更快。

选择较慢的编程语言(如Python)可能导致较长的执行时间,尤其是对于计算密集型任务。

4、编译程序产生的机器代码质量:

高质量的编译器可以将源代码优化为更有效率的机器代码,从而减少执行时间。

低质量的编译器可能无法实现最佳性能,导致执行时间增加。

5、计算机执行指令的速度:

更快的处理器和更快的内存访问速度可以加速算法的执行。新一代硬件通常比旧硬件执行得更快。如果硬件性能较低,算法执行时间可能较长。

前两者都是与设计方法有关的,而后三者与运行程序的计算机软硬件环境有关。

现在让我们抛开与计算机软硬件相关的因素,那么影响算法时间效率的最主要问题就是问题规模

问题规模通常指算法的输入量,我们一般用整数 n 表示。例如:用相同的算法对10个元素进行处理和对10000000……个数据进行处理显然是不同的。

一个算法的时间开销与算法中语句的执行次数成正比。算法中语句执行次数越多,它的时间开销就越大。此时就涉及到语句频度的概念。

那么,什么是语句频度

一个算法中的语句执行次数称为语句频度。

一般情况下,算法中基本运算执行次数用 T(n) 表示,若有问题规模 n 的某个函数 f(n),使存在自然数 n0,正常数 c,当 n >= n0 时,T(n) <= cf(n),则称 f(n) 是 T(n) 的渐进上界,记为:

T(n) = O( f(n) )

大O记号表示算法的一种渐进时间复杂度,也常简称为时间复杂度。

渐进时间复杂度用以表达一个算法运行时间的上界,估计算法的执行时间的数量级。

终于,我们可以得到渐进时间复杂度的具体定义了……

渐进时间复杂度是一种用于分析算法性能的概念,它描述了算法在处理不同规模的输入数据时所需的计算资源(主要是时间)的增长趋势。通常用大O记号(O-notation)来表示,它提供了算法运行时间的上界估计,表示算法的运行时间不会超过某个特定函数的增长速度。

时间复杂度的几个性质

1. 渐进性:时间复杂度是一种渐进性描述,它关注的是问题规模 n 趋向无穷大时的增长趋势。因此,我们通常关心的是算法在大规模数据上的性能表现,而不是精确的运行时间。

2. 上界估计:时间复杂度表示的是最坏情况下的运行时间上界,即在所有可能的输入情况下,算法的运行时间都不会超过 f(n)。这有助于我们对算法的性能有一个保守的估计。

3. 大O记号:大O记号(O-notation)是常用的表示时间复杂度的符号。例如,如果一个算法的时间复杂度为 O(n),则表示算法的运行时间与问题规模 n 成线性增长。

4. 简化比较:时间复杂度的目的是简化算法之间的比较,而不是精确测量运行时间。通过使用大O记号,我们可以更容易地确定哪个算法在大规模数据上更有效率,而不需要具体的测量。

5. 选择合适的算法:时间复杂度帮助我们选择最适合特定问题的算法。例如,如果一个算法的时间复杂度是 O(n^2),而另一个是 O(n logn),在大规模数据上,后者通常更快。

时间复杂度是算法分析的重要工具,它允许我们估计算法在不同规模的输入数据上的性能表现,并帮助我们选择最优算法以解决特定问题。在算法设计和分析中,理解时间复杂度是提高计算效率和优化算法的关键一步。

渐进估算

一般情况下,我们可以通过考察一个程序中的关键操作(对算法执行时间贡献最大的操作的执行次数来计算算法的时间按复杂度。

了解了这一点,我们就可以来看看具体该如何计算一个算法的时间复杂度:

  1. 确定基本操作:首先,我们要确定算法中最主要、最耗时的操作——通常称为关键操作。这些操作是时间复杂度分析的核心。

  2. 建立基本操作的执行次数表达式:为了计算时间复杂度,我们需要建立一个表示关键操作执行次数的表达式,通常用 T(n) 来表示,其中 n 是问题规模。

  3. 简化表达式:在这一步,需要将表达式简化,去掉不必要的常数系数和低次项。这是为了关注算法在大规模数据上的增长趋势,而不是具体的执行时间。为了简化表达式,可以遵循以下规则:

    • 常数系数可忽略:常数系数不影响算法的增长趋势,所以通常可以省略不写。例如,3n^2 和 n^2 在时间复杂度分析中被认为是相同的。

    • 保留最高次项:只考虑表达式中的最高次项,因为在大规模数据上,它将主导增长趋势。其他次项、常数项和低次项都可以忽略。

  4. 确定时间复杂度符号:根据简化后的表达式,确定时间复杂度的大O记号。这是时间复杂度的最终表示,它表示算法在不同规模数据上的性能增长趋势。例如,如果简化后的表达式为 T(n) = 2n^2 + 3n + 1,那么时间复杂度可以表示为 O(n^2),因为 n^2 是最高次项。

好了,那么现在我就要向你就“简化表达式”提一个问题:

数程序步来判定算法的时间效率不是更精细吗?为啥还得在此基础上去掉低次幂和系数?

去掉低次幂还可以理解,因为随着问题规模趋向于正无穷大的时候,低次幂项值可以忽略

但是系数为何要去掉?

"程序步" :计算机科学和编程中的一个概念,通常用于描述算法或程序执行的基本操作或指令。程序步可以理解为算法执行的离散操作,每个步骤代表一个基本操作,例如赋值、条件判断、循环迭代、函数调用等。程序步用于描述算法的执行顺序和流程。

程序步的概念有助于分析和比较不同算法的性能。通过计算算法执行所需的程序步数,可以估算算法的时间复杂度。一般来说,执行步数越少,算法越高效。

在算法分析中,去掉低次幂项和系数的目的是为了关注算法的渐进行为,而不是关注具体输入大小或常数因子。这是因为算法的性能分析的主要关注点是如何随着输入规模的增长而变化,而不是具体的绝对性能。

去掉系数的原因是,系数通常取决于具体的实现、硬件、编程语言等因素,它们并不是算法本身的性质。通过去掉系数,我们可以更好地比较不同算法之间的性能,而不受特定实现的影响。

例如,如果有两个算法,一个算法A的运行时间是3n,另一个算法B的运行时间是1000n,如果我们只关注系数,似乎算法A更好。但如果我们去掉系数,它们的运行时间都是O(n),这意味着它们在输入规模增加时的性能表现是相同的。

因此,去掉系数和低次幂项是为了提供一种更通用、更抽象的方式来比较和分析不同算法的性能,以便更好地理解它们如何在大规模输入下表现。这有助于选择最适合特定问题的算法,而不受具体实现或硬件的限制。

常见的渐进时间复杂度排序

常见的渐进时间复杂度从小到大依次是:

1. O(1) —— 常数时间复杂度:
表示算法的执行时间是一个常数,不随问题规模的增加而变化。这通常是一些基本操作,如赋值、比较、算术运算等的执行时间。

2. O(log n) —— 对数时间复杂度:
表示算法的执行时间与问题规模的对数成正比。常见的例子是二分查找算法,其中问题规模每次减半。

3. O(n) —— 线性时间复杂度:
表示算法的执行时间与问题规模成线性关系。例如,对一个包含 n 个元素的数组进行线性扫描的操作。

4. O(n log n) —— 线性对数时间复杂度:
表示算法的执行时间介于线性时间和平方时间之间,通常出现在一些高效的排序算法中,如快速排序和归并排序。

5. O(n^2) —— 平方时间复杂度:
表示算法的执行时间与问题规模的平方成正比。通常出现在嵌套循环的情况下,如选择排序和插入排序。

6. O(n^k) —— 多项式时间复杂度:
表示算法的执行时间与问题规模的 k 次方成正比。k 可能是大于 2 的正整数,这意味着算法在大规模数据上的性能下降较快。

7. O(2^n) —— 指数时间复杂度:
表示算法的执行时间以指数方式增长,通常出现在某些穷举算法或递归算法中。

8. O(n!) —— 阶乘时间复杂度:
表示算法的执行时间与问题规模的阶乘成正比。这是一种极端情况,通常只出现在极其低效的算法中,对于大规模问题来说几乎不可行。

9. O(n^n) —— 指数级时间复杂度:

表示算法的执行时间与问题规模的 n 的 n 次方成正比。这是一种非常高的时间复杂度,通常只出现在极其低效的算法中,对于大规模问题来说几乎不可行。这种时间复杂度通常表明算法的性能随着问题规模的增加而急剧下降。

选择适当的时间复杂度是算法设计中的关键步骤,我们需要综合考虑问题规模、可用资源以及问题的特点。

通常,我们希望选择时间复杂度较低的算法,以在处理大规模数据时获得更高的效率。

然而,你需要记住,时间复杂度只是一个理论估计,实际的性能还受到硬件、编程语言、编译器等因素的影响。

你可能感兴趣的:(易错知识点,数据结构初阶,算法,时间复杂度,数据结构,渐进估算)