数据结构复杂度的分析

前言

在进行数据结构的学习过程中,我们常常需要对代码进行复杂度的分析,这个复杂度的分析有两种,一种是时间复杂度,一种是空间复杂度,在分析这两个之前,我们先来谈谈,我们为什么要进行空间和时间复杂度的分析呢?

为什么要进行复杂度的分析

你可能会说,我把代码跑一遍,通过统计、监控,就能得到算法执行的时间和占用的内存大小。为什么还要做时间、空间复杂度分析呢?这种分析方法能比我实实在在跑一遍得到的数据更准确吗?为什么还要自己分析它的复杂度呢?

1. 测试结果非常依赖测试环境

测试环境中硬件的不同会对测试结果有很大的影响。比如,我们拿同样一段代码,分别用 Intel Core i9 处理器和 Intel Core i3 处理器来运行,不用说,i9 处理器要比 i3 处理器执行的速度快很多。还有,比如原本在这台机器上 a 代码执行的速度比 b 代码要快,等我们换到另一台机器上时,可能会有截然相反的结果。

2. 测试结果受数据规模的影响很大

就比如说排序算法,对同一个排序算法,待排序数据的有序度不一样,排序的执行时间就会有很大的差别。极端情况下,如果数据已经是有序的,那排序算法不需要做任何操作,执行时间就会非常短。除此之外,如果测试数据规模太小,测试结果可能无法真实地反映算法的性能。比如,对于小规模的数据排序,插入排序可能反倒会比快速排序要快!
由此我们可以知道,想要知道算法的执行效率,我们必须要自己进行复杂度的分析。

复杂度分析法则

  1. 单段代码看高频:比如循环。
  2. 多段代码取最大:比如一段代码中有单循环和多重循环,那么取 多重循环的复杂度。
  3. 嵌套代码求乘积:比如递归、多重循环等
  4. 多个规模求加法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加。

时间复杂度的分析

大 O 这种复杂度表示方法只是表示一种变化趋势。我们通常会忽略掉公式中的常量、低阶、系数,只需要记录一个最大阶的量级就可以了。所以,我们在分析一个算法、一段代码的时间复杂度的时候,也只关注循环执行次数最多的那一段代码就可以了。这段核心代码执行次数的 n 的量级,就是整段要分析代码的时间复杂度。
我们常见的几种就有以下几种:

其中想讲一下O(1),只要代码的执行时间不随 n 的增大而增长,这样代码的时间复杂度我们都记作 O(1)。或者说,一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1)。
再稍微提一下O(logn),下面这个就是这个复杂度,
2^i=n ----> i=log2n.

i=1; 
while (i <= n) 
{
    
    
 i = i * 2;
}

细分时间复杂度

  1. 最好情况时间复杂度:代码在最理想情况下执行的时间复杂度。
  2. 最坏情况时间复杂度:代码在最坏情况下执行的时间复杂度。
  3. 平均时间复杂度:用代码在所有情况下执行的次数的加权平均值表示。
  4. 均摊时间复杂度:在代码执行的所有复杂度情况中绝大部分是低级别的复杂度,个别情况是高级别复杂度且发生具有时序关系时,可以将个别高级别复杂度均摊到低级别复杂度上。基本上均摊结果就等于低级别复杂度。

如何使用平均时间复杂度和均摊时间复杂度

  1. 平均时间复杂度
    代码在不同情况下复杂度出现量级差别,则用代码所有可能情况下执行次数的加权平均值表示。
  2. 均摊时间复杂度
    两个条件满足时使用:1)代码在绝大多数情况下是低级别复杂度,只有极少数情况是高级别复杂度;2)低级别和高级别复杂度出现具有时序规律。均摊结果一般都等于低级别复杂度。

空间复杂度

常见的就3个:
O(1)
O(n)
O(n^2)
分析:

void print(int n) 
{
    
    
	 int i = 0; 
	 int[] a = new int[n];
	 for (i; i = 0; --i)
	 {
    
    
	 	print out a[i]
	 }
}

第 2 行代码中,我们申请了一个空间存储变量 i,但是它是常量阶的,跟数据规模 n 没有关系,所以我们可以忽略。第 3 行申请了一个大小为 n 的 int 类型数组,除此之外,剩下的代码都没有占用更多的空间,所以整段代码的空间复杂度就是 O(n)。

至此,关于复杂度的分析到此结束,想要完全掌握复杂度的分析,必须自己多进行分析,希望可以给大家帮助,如果方便的话,不妨点个赞呗。

猜你喜欢

转载自blog.csdn.net/Freedom_cao/article/details/107285243