今天也是很开心的学到了数据结构,也是打算把我自己对知识的理解给写出来了。第一篇数据结构开始咯。开始之前我们先理解一个概念。
什么是算法效率?
算法效率是指算法执行的速度或完成任务所需的资源(如时间和空间)的度量。它通常用于评估算法的性能和比较不同算法的优劣。
算法效率的主要考量因素包括:
1. 时间复杂度:描述算法执行所需的时间与问题规模(如输入数据的大小)之间的关系。常见的时间复杂度度量方法有 O(n)、O(log n)、O(n log n) 等。较低的时间复杂度表示算法在处理大规模问题时效率更高。
2. 空间复杂度:描述算法所需的额外存储空间与问题规模之间的关系。较低的空间复杂度表示算法在内存使用上更高效。
3. 稳定性:某些算法可能对输入数据的顺序或其他特征有特殊要求,稳定性指的是算法在这些情况下的表现。
4. 可扩展性:算法是否容易适应更大规模或更复杂的问题。
5. 实际性能:除了理论分析,实际测试和基准测试也可以用来衡量算法在具体硬件和环境中的效率。
评估算法效率的目的是选择在特定场景下最适合的算法,以达到最优的性能和资源利用。在设计算法时,通常会追求高效率的算法,以减少计算时间和资源消耗。同时,还需要考虑算法的实现复杂度、可读性和可维护性等因素。对于一些复杂问题,可能需要在效率和其他因素之间进行权衡。
但是现在我们不了解那么多,我们就来理解一下简单的时间复杂度和空间复杂度!!!
时间复杂度
时间复杂度是一种对算法运行时间的度量,用于描述算法在处理特定规模问题时所需的计算操作数量。它反映了算法的效率和性能。
时间复杂度通常使用大 O 表示法来表示,其中 O(n) 表示算法的运行时间与问题规模 n 成正比。例如,如果一个算法的时间复杂度为 O(n),意味着随着问题规模的增大,算法的运行时间也会线性增加。
常见的时间复杂度级别包括:
1. O(1):表示常量时间复杂度,算法的运行时间与问题规模无关,无论输入规模如何变化,算法的执行时间都是固定的。
2. O(log n):对数时间复杂度,算法的运行时间与对数函数相关,对于大规模问题,算法的效率较高。
3. O(n):线性时间复杂度,算法的运行时间与问题规模成线性关系。
4. O(n log n):线性对数时间复杂度,算法的运行时间与 n 和 log n 相关。
5. O(n^2):平方时间复杂度,算法的运行时间与 n 的平方成正比,对于大规模问题,效率可能较低。
6. O(n^c):其中 c 是常数,这种复杂度表示算法的运行时间与 n 的 c 次幂成正比。
这里需要注意的是:时间复杂度的评估是基于算法的最坏情况运行时间,即在最不利的输入情况下的时间复杂度。它提供了一种对算法效率的粗略估计,帮助我们在不同算法之间进行比较和选择。
时间复杂度只是一种理论上的度量,实际的运行时间还受到其他因素的影响,如硬件性能、数据结构的选择、算法的实现细节等。因此,在实际应用中,除了考虑时间复杂度外,还需要综合考虑其他因素来评估算法的性能。
通过分析算法的时间复杂度,我们可以在设计和选择算法时做出更明智的决策,以满足特定场景下对效率的要求。较低的时间复杂度通常意味着算法在处理大规模问题时具有更好的性能。但在实际情况中,还需要结合具体问题和需求来选择合适的算法。
下面我们就开始用例子来说说如何计算时间复杂度。
例一:
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int Func(int n) { int i = 0; int j = 0; int count = 0; for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { ++count; } } int k = 0; for (k = 0; k < 2 * n; k++) { ++count; } int a = 20; while (a) { ++count; --a; } return count; } int main() { int n = 0; scanf("%d", &n); int count = Func(n); printf("%d\n", count); return 0; }
在上面代码中大家觉得Func函数的时间复杂度是多少呢?下面就说一下时间复杂度的算法。我们把图一看就都理解了。
上图显示Func函数执行基本操作的次数,那有什么用呢?
其实在计算时间复杂度时我们可以用执行次数的表达式,用大 O渐近表示法,来表示时间复杂度,但随着n的值变大,我们发现其实对次数变化影响最大的是 n*n这一项,而且时间复杂度是估算的,所以我们就剔除掉后面影响不大的两项,取影响最大的进行估算。所以这个函数的时间复杂度就是 O(n^2).
例二:
int Func1(int n ,int m ) { int i = 0; int j = 0; int count; for ( i = 0; i < k; i++) { for (j = 0; j < m; j++) { ++count; } } return count; }
我们列出数学表达式计算次数:F(n)=n+m. 这里的时间复杂度就是O(n+m)
需要注意的是:如果题目给出n远大于m,那么此时的时间复杂度就是O(n)。
例三:
int Func2( ) { int i = 0; int j = 0; int count; for ( i = 0; i < 10; i++) { for (j = 0; j < 100; j++) { ++count; } } return count; }
这里列出数学表达式F(n)=10*100=1000。大家是不是以为函数Func2的
时间复杂度为O(1000)了,其实不然,在计算机中我们把常数次基本操作的函数的时间复杂度统一设定为O(1)。故func2的时间复杂度其实为O(1)在计算中这种复杂度是最优的了.
例四:
int Func4(arr[],int sz,int x) { int begin = 0; int end = sz; int n = 0; while (end>begin) { int mid = (begin + end) / 2; if (arr[mid] > x) { end = mid - 1; } else if (arr[mid] < x) { begin = mid + 1; } else return mid; }
列出数学表达式F(n)=log 2 n,但在计算机中这里的底数难以表示,所以在计算机中就自动把底数省略掉,所以这里的空间复杂度就是O(log n) 。
空间复杂度
空间复杂度是对算法在执行过程中所需的额外存储空间的度量。它用于评估算法对内存的使用效率。
与时间复杂度类似,空间复杂度通常使用大 O 表示法来表示。空间复杂度描述了算法在解决特定问题时所需的存储空间与问题规模之间的关系。例如,O(n) 表示算法的空间复杂度与问题规模 n 成正比。
空间复杂度主要考虑以下几个方面:
1. 额外的存储空间:算法可能需要额外的存储来保存中间结果、临时变量或其他数据结构。
2. 输入数据的规模:问题的输入规模可能直接影响算法所需的空间。
3. 数据结构的选择:不同的数据结构可能具有不同的空间复杂度,例如使用数组和使用链表可能会有不同的空间需求。
4. 递归算法:递归算法在执行过程中可能会产生大量的栈空间消耗。
较低的空间复杂度意味着算法在内存使用上更高效,尤其在处理大规模问题或资源受限的环境中更为重要。然而,在实际情况中,需要在空间复杂度和算法的其他特性(如时间复杂度、可读性等)之间进行权衡。
与时间复杂度一样,空间复杂度的评估是基于算法的最坏情况,并且是一种理论上的估计。实际的空间需求可能受到具体实现、硬件环境和问题特征的影响。
在设计算法时,了解和优化空间复杂度可以帮助避免不必要的内存消耗,并提高算法的整体效率。但需要根据具体问题和系统资源来综合考虑空间复杂度与其他因素的平衡。有时候,为了获得更好的时间复杂度或其他优势,可能会接受较高的空间复杂度。
了解完后,我们继续举例:
例5:
int Func(int n) { int i = 0; int j = 0; int count = 0; for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { ++count; } } int k = 0; for (k = 0; k < 2 * n; k++) { ++count; } int a = 20; while (a) { ++count; --a; } }
空间复杂度与时间复杂度计算方法相似,不同的是空间复杂度是用计算变量的个数来估算空间复杂
度,也是用大O 表示法来表示。如上面中我们看到此函数创建的变量的数量为6个,即n,i,j,
k,count,a。与时间复杂度一样常数用O(1)表示,故函数的空间复度为O(1).
例五:
int Func5(int n) { if (n == 1) { return 1; } else { return n * Func5(n - 1); } }
像这里运用·递归来算n的阶乘,我们知道在它每次递推过程中都会在栈区开辟一块空间,在此函
数中共递推n次,故其空间复杂度为O(n)。
总之:空间复杂度和时间复杂度相似只不过一个通过计算变量个数来估算,一个是计算执行基本次
数来估算,都是用大O 渐近表示法来表示,且都是取影响最大的项,剔除掉影响较小的项,有的情
况也需根据实际情况进行改变。
今天这篇博客就写到这了。