如何分析,统计算法的执行效率和资源消耗?

如何分析,统计算法的执行效率和资源消耗?

数据结构与算法其根本是解决”快“和”省“的问题,就是要想马儿不吃草,又想马儿跑得快,即如何让代码运行得更快,如何让代码更省存储空间。因此,执行效率这一概念便是算法一个非常重要的考量指标。

其实,只要提到数据结构与算法,就一定无法脱离时间,空间复杂度来分析。而且,一个老师说过:复杂度分析是整个算法学习的精髓,只要掌握了它,数据结构与算法的内容基本上就掌握了一半。

为什么我们需要复杂度分析呢? 为什么我们不直接把代码跑一边,通过统计和监控,就能得到算法执行时间和占用的内存大小。为什么还需要时间和空间复杂度分析呢?

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

测试环境中硬件不同对测试结果有着巨大的影响。比如i9和i3处理器运行一段代码,毫无疑问的i9要比i3快很多。

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

比如说排序算法,对于同一个排序算法,待排序数据的有序度不一样,排序的执行时间就会有很大的差别。极端情况下,数据本就有序,那排序算法就不需要什么操作,执行时间就会非常之短。比如说,小规模的数据排序,插入排序可能要比快速排序要快。

所以,我们需要一个不用具体数据赖测试,就可以大致地估计算法的执行效率的方法。这既是时间复杂度与空间复杂度分析

下面我们看一段代码

int cal(int n) {
	int sum = 0;
	int i = 1;
	for (; i <= n; ++i) {
		sum = sum + i;
	}
	return sum;
}

这段代码非常简单,就是求1—>n的累加和。

从CPU的角度来看,这段代码的每一行都执行着类似的操作:读数据-运算-写数据。尽管每行代码对应的CPU执行的个数和时间不一样,但是,这里我们粗略估计每行代码时间都一样,所以总执行时间便是(2n+2)。 也就是 2行和3行执行一次。4行和5行执行了n次。

int cal(int n) {
	int sum =0;
	int i = 1;
	int j = 1;
	for (; i <= n; ++i) {
		j = 1;
		for(; j <= n; ++j) {
			sum = sum + i * j;
		}
	}
}

这里第2,3,4行代码,每行执行一次(3),第5,6行代码循环执行了n遍(2n), 第7,8行代码执行了n2 遍(2n2),总体来说就是 (2n2+2n+3)。用大O表示法便是O(n2),也称为时间复杂度。

时间复杂度分析

时间复杂度分析主要有这几个诀窍:

1.只关注循环执行次数最多的一段代码

int cal(int n) {
   int sum = 0;
   int i = 1;
      for (; i <= n; ++i) { 
          sum = sum + i;   
          }   
   return sum;
}

比如这一个代码的时间复杂度就是O(n)。我们只需要关注for循环中代码执行的次数就好。

2.加法法则:总复杂度等于量级最大的那段代码的复杂度

int cal(int n) 
{  
   int sum_1 = 0;
   int p = 1;
      for (; p < 100; ++p) {
          sum_1 = sum_1 + p;   
		}
   int sum_2 = 0;
   int q = 1; 
   for (; q < n; ++q) {
       sum_2 = sum_2 + q; 
    }
   int sum_3 = 0;
   int i = 1;
   int j = 1;
   for (; i <= n; ++i) {
        j = 1; 
        for (; j <= n; ++j) { 
        sum_3 = sum_3 +  i * j;     
        }  
	}
     return sum_1 + sum_2 + sum_3; 
 }

这个代码分为三个部分,分别求sum1,sum2,sum3。我们可以分析每一段的复杂度如何将其放一块,再取一个量级最大的作为整段代码的复杂度。

这里整段代码时间复杂度是O(n2),第一个小for循环块是100次,是常量级别的执行时间。 第二段和第三段分别是O(n)和O(n2)。所以,我们取最大的量级,整段代码的时间复杂度便是O(n2)。也就是说:总的时间复杂度分析就等于量级最大那段代码的时间复杂度

3.乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

也就是说如果外层for循环是n次,该外层for循环里还有一个循环次数是n次的for循环。两个for循环嵌套便是n*n,时间复杂度便是O(n2)。落到实际代码上便是。

	int cal(int n) {
	int ret = 0;
	int i = 1;
	for (; i < n; ++i) {
		ret = ret + f(i);
	}
} 
	
 
	int f(int n) {
	int sum = 0;
	int i = 1;
	for (; i < n; ++i) {
		sum = sum + i;
}
	return sum; 
}

这段代码的时间复杂度便是O(n2)。

几种常见的时间复杂度量级

在这里插入图片描述
图片源于网络侵权联系删除。

1.O(1)

 int i = 8;
 int j = 6;
 int sum = i + j;

这里要强调的一点是这段代码即便又三行它的时间复杂度也是O(1),而不是O(3),只要是常量级的都用O(1)表示。

2.O(logn)、O(nlogn)

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

从代码中可以看出,变量 i 的值从 1 开始取,每循环一次就乘以 2。当大于 n 时,循环结 束。还记得我们高中学过的等比数列吗?实际上,变量 i 的取值就是一个等比数列。如果我 把它一个一个列出来,就应该是这个样子的:

	**2^0^		2^1^    2^2^ ...2^k^ ... 2^x^ = n**

所以,我们只要知道 x 值是多少,就知道这行代码执行的次数了。通过 2 =n 求解 x 这个 问题我们想高中应该就学过了,我就不多说了。x=log n,所以,这段代码的时间复杂度就 是 O(log n)。

现在再来看另一段代码

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

根据刚刚的思路,很简单就能看出来,这段代码的时间复杂度为 O(log3n)。

3. O(m+n)、O(m*n)

先看下面的一段代码

int cal(int m, int n) {
	int sum_1 = 0;
	int i = 1;
	for (; i < m; ++i) {
	sum_1 = sum_1 + i;
}
	int sum_2 = 0;
	int j = 1;
	for (; j < n; ++j) {
		sum_2 = sum_2 + j;
}
	return sum_1 + sum_2; }

从代码中可以看出,m 和 n 是表示两个数据规模。我们无法事先评估 m 和 n 谁的量级 大,所以我们在表示复杂度的时候,就不能简单地利用加法法则,省略掉其中一个。所以, 上面代码的时间复杂度就是 O(m+n)。

针对这种情况,原来的加法法则就不正确了,我们需要将加法规则改为:T1(m) + T2(n) = O(f(m) + g(n))。但是乘法法则继续有效:T1(m)*T2(n) = O(f(m) * f(n))。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页