如何评价一个算法的好坏?
如果单从执行效率上进行评估,可能会想到这么一种方案
- 比较不同算法对同一组输入的执行处理时间
- 这种方案也叫做:事后统计法
下面例举斐波那契数列的例子
我们知道类似于 0 1 1 2 3 5 8…这样的数列叫斐波那契数列,对于如何求斐波那契数列中的某一个值,大家普遍的做法是采用递归,即F(n)=F(n-1)+F(n+1);如下
public static int fib1(int n) {
//n<=1,第一个数和第二个数都是本身
if (n <= 1) return n;
//采用递归,所要的值就是前面两个数之和
return fib1(n - 1) + fib1(n - 2);
}
但这样的算法非常耗运行时间,为此我们准备另外一种算法;
public static int fib2(int n) {
if (n <= 1) return n;
int first = 0;
int second = 1;
//当求第n(n>1)个数的值,要加n-1次
for (int i = 0; i < n - 1; i++) {
int sum = first + second;
first = second;//上一次相加的第二个数是下一次相加的第一个数
second = sum;//上一次相加的两个数之和是下一次相加的第二个数
}
return second;
}
这个算法的执行效率非常高,运行时间几乎为0。
如下是两种算法的执行时间对比,设置当n=45时,第一种算法执行时间为6.35秒,真的很慢。。。并且随着n增大,会越来越慢,而第二种算法执行时间几乎为0秒。
上述方案有比较明显的缺点
- 执行时间严重依赖硬件以及运行时各种不确定的环境因素
- 必须编写相应的测试代码
- 测试数据的选择比较难保证公正性
一般从以下维度来评判算法的优劣
- 正确性,可读性,健壮性(对不合理的输入的反应能力和处理能力)
- 时间复杂度(time complexity):估算程序指令的执行次数(执行时间)
- 空间复杂度(space complexity):估算所需占用的存储空间
一个好的算法最基本的条件就是满足正确性,可读性,健壮性,其次时间复杂度低或空间复杂度低。
那么如何估算时间复杂度呢?
一般用大O表示法来描述复杂度,它表示的是数据规模n对应的复杂度
- 忽略常数,系数,低阶
- 9 > > O(1)
- 2n+3 > > O(n)
- 对数阶一般省略底数
下面举相关例子
public static void test(int n){
//执行次数为1,时间复杂度为O(1)
if (n>10){
System.out.println("n>10");
}else if (n>5){
System.out.println("n>5");
}else {
System.out.println("n<=5");
}
}
public static void test2(int n){
//执行次数1+3n,时间复杂度为O(N)
for (int i = 0;i<n;i++){
System.out.println("test");
}
}
public static void test3(int n){
//执行次数为3n^2+3n+1,时间复杂度为O(n^2)
for (int i=0;i<n;i++){
for (int j=0;j<n;j++){
System.out.println("test");
}
}
}
public static void test4(int n){
//执行次数为48n+1,时间复杂度为O(N)
for (int i =0;i<n;i++){
for (int j =0;j<15;j++){
System.out.println("test");
}
}
}
public static void test5(int n){
//执行次数为log2(n),时间复杂度为O(logn)
while((n=n/2)>0){
System.out.println("test");
}
}
public static void test6(int n){
//执行次数1+3log2(n)+2*nlog2(n),时间复杂度为O(nlogn)
for (int i=1;i<n;i=i*2){
for (int j=0;j<n;j++){
System.out.println("test");
}
}
}
时间复杂度大小排序
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n)
那么我们现在可以估算斐波那契数列两种算法的时间复杂度,第二种算法的时间复杂度为O(n),而第一种算法的时间复杂度为O(2^n), 可以说差别非常大了。