目录
1.如何衡量一个算法的好坏
下面是求斐波那契数列的算法好还是不好,我们应当如何衡量好与坏,它的标准是什么?
//斐波那契数列
public class Test {
public static int fib(int n) {
if(n < 3) {
return 1;
}
return fib(n-1) +fib(n-2);
}
public static void main(String[] args) {
System.out.println(fib(5));
}
}
2.算法效率
算法效率分析分为两种:第一种是时间效率,第二种是空间效率。时间效率被称为时间复杂度,而空间效率被称为空间复杂度。时间复杂度主要衡量一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间。在计算机发展的早期,计算机的储存容量很小,所以当时很在乎空间复杂度。但是经过长久的发展,计算机的储存容量已经达到了很高的程度,现在来说,时间复杂度越来越受到重视。
3.时间复杂度
3.1时间复杂度的概念
时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个数学函数,它定量描述了该算法的运行时间。但是计算机运行的时间是非常短的,且每次测试计算的状态和不同的计算机都会影响计算机的运行时间,所以我们使用真实的运行时间来描述时间复杂度是没有意义的,也是没有很高的参考价值。但是我们发现一个算法所花费的时间与其语句的执行次数成正比,算法中的基本操作的执行次数,为算法的时间复杂度。
3.2大O的渐进表示法
//时间复杂度
public class Test {
public static void main(String[] args) {
int n = 100;
int count = 0 ;
for (int i = 0; i < n; i++) {
count++;
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
count++;
}
}
for (int i = 0; i < 10; i++) {
count++;
}
System.out.println(count);
}
}
这段代码的基本操作次数为:
F(N) = N^2 + N + 10;
实际中我们计算时间复杂度时,不需要如此精确的计算执行的次数,只需要大概的执行次数,那么我们使用大O的渐进表示法。
3.3什么是大O阶方法
- 用常数1取代运行时间中的所有加法常数(去常数);
- 在修改后的运行次数函数中,只保留最高阶的项(保留最高阶的项);
- 如果最高阶的项存在且不是1,则去除这个项目相乘的常数(去掉最高阶的项的系数);
使用大O的渐进法以后,上述代码的时间复杂度即为:O(N^2)
通过上述的操作我们发现大O的渐进表示法去掉了那些对结果影响不大的项,简明扼要的表示了执行的次数。
另外有些算法的时间复杂度存在最好、平均和最坏的情况:
最好情况:任意输入规模的最小运行次数(下界);
平均情况:任意输入规模的期望运行次数;
最坏情况:任意输入规模的最大运行次数(上界)。
在实际中一般情况关注的是算法的最坏运行情况,所以我们日常中常说的时间复杂度指的是最坏情况下的时间复杂度。
3.4常见时间复杂度的计算
【实例一】
//常见的时间复杂度
//实例一
public class Test {
public static void func2(int n) {
int count = 0;
for (int i = 0; i < 2*n; i++) {
count++;
}
int m = 10;
while((m--) > 0) {
count++;
}
System.out.println(count);
}
}
【解析】总的执行次数是2*n+10,时间复杂度是O(N);
【实例二】
//实例二
public static void func3(int n, int m) {
int count = 0;
for (int i = 0; i < m; i++) {
count++;
}
for (int i = 0; i < n; i++) {
count++;
}
System.out.println(count);
}
【解析】总的执行次数是m+n,时间复杂度是O(N+M)
【实例三】
//实例三
public static void func4(int n) {
int count = 0;
for (int i = 0; i < 1000; i++) {
count++;
}
System.out.println(count);
}
【解析】总的执行次数是1000,不管传过来的参数的值是多少,总的执行次数是不会变的,时间复杂度是O(1)
【实例四】
//实例四
public static void func5(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;
}
}
}
【解析】总的执行次数是(n-1)+(n-2)+(n-3)+...+1=n*(n-1)/2,时间复杂度是O(n^2)
【实例五】
//实例五
public static int func6(int[] array, int value) {
int begin = 0;
int end = array.length - 1;
while(begin <= end) {
int mid = (begin + end) / 2;
if(array[mid] < value) {
begin = mid + 1;
} else if(array[mid] > value) {
end = mid - 1;
} else {
return mid;
}
}
return -1;
}
【解析】总的执行次数是logN,时间复杂度是O(logN) 。(这里指的是以2为底)
【实例六】
//实例六
public static int func7(int n) {
return n < 2 ? n : func7(n - 1) * n;
}
【解析】总的执行次数是N,时间复杂度是O(N)
【实例七】
//实例七
public static int func8(int n) {
return n < 2 ? n : func7(n - 1) + func7(n - 2);
}
【解析】总的执行次数大约是2^0 + 2^1 + 2^2 + 2^3 +...+ 2^n,时间复杂度是O(2^N)
4.空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。空间复杂度并不是指程序占用了多少的空间,而是指临时占用储存空间的大小。空间复杂度也是使用大O渐进表示法。
【实例一】
//空间复杂度的计算
//实例一
public class Test {
public static void func1(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)
【实例二】
//实例二
public static long[] func2(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)
【实例三】
//实例三
public static long func3(int n) {
return n < 2 ? n : func3(n - 1) * n;
}
【解析】开辟了N个栈帧,每个栈帧使用了常数个空间,空间复杂度是O(N)