算法效率分为两种,一种是时间效率,一种是空间效率,时间效率又称时间复杂度,空间效率又称空间复杂度。时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量的是算法额外需要的运行空间。在计算机才初步发展的年代,计算机的存储容量非常小,人们对算法空间复杂度的重视程度远远大于时间复杂度,但随着计算机技术的迅速发展,计算机的内存容量已经有了非常大的提高,所以现在我们对算法的时间复杂度的关注程度更高。
在计算机科学中,算法的时间复杂度是一个函数,它定量描述了算法的运行时间。从理论上来说,一个算法的运行时间是不能算出来的,只有将程序运行起来才能知道运行时间,而且同一个程序在不同的环境内运行时间也有可能不同,何况如果我们每个程序通过上机测试来查找运行时间,那也太麻烦了一些,于是,我们引入了时间复杂度这个概念。
一个算法的时间复杂度指的是算法中基本操作的执行次数。
分析一下func1()方法中循环操作执行了多少次
void func1(int N){ int count = 0; for (int i = 0; i < N ; i++) { //双重循环共执行n*n次 for (int j = 0; j < N ; j++) { count++; } } for (int k = 0; k < 2 * N ; k++) { //单重循环共执行2*n次 count++; } int M = 10; while ((M--) > 0) { //单重循环共执行10次 count++; } System.out.println(count); }
func1()共执行的基本操作次数:
F(N) = N^2 + 2*N + 10
当 N=10时,F(N)=130
当 N=100时,F(N)=10210
当 N=1000时,F(N)=1002010
实际中我们计算时间复杂度时,其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么就可以采用大 O 的渐进表示法。
大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
使用大 O 的渐进表示法后,func1()的时间复杂度为: O(N^2)
当N = 10时,F(N) = 100
当N = 100时, F(N) = 10000
当N = 1000时, F(N) = 1000000
通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
例如:在一个长度为N数组中搜索一个数据x
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间,这样计算并无意义,所以空间复杂度算的是变量的个数。
空间复杂度计算规则与时间复杂度类似,也使用大O渐进表示法。
实例一:
// 计算bubbleSort的空间复杂度? void bubbleSort(int[] array) { for (int end = array.length; end > 0; end--) { boolean sorted = true; for (int i = 1; i < end; i++) { if (array[i - 1] > array[i]) { Swap(array, i - 1, i); sorted = false; } } if (sorted == true) { break; } } }
使用了常数个额外空间,所以空间复杂度为 O(1)
实例二:
// 计算fibonacci的空间复杂度? int[] fibonacci(int n) { long[] fibArray = new long[n + 1]; fibArray[0] = 0; fibArray[1] = 1; for (int i = 2; i <= n ; i++) { fibArray[i] = fibArray[i - 1] + fibArray [i - 2]; } return fibArray; }
动态开辟了N个空间,空间复杂度为 O(N)
实例三:
// 计算阶乘递归Factorial的时间复杂度? long factorial(int N) { return N < 2 ? N : factorial(N-1)*N; }
递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间。空间复杂度为O(N)