一、算法效率
解决问题的方法多种多样,但是算法也有优劣之分,那如何衡量一个算法的好坏呢?
这就要看算法效率了。
算法效率分析分为两种:
第一种是 时间效率 被称为时间复杂度
第二种是 空间效率 被称作空间复杂度
时间复杂度主要衡量的是一个算法的 运行速度
空间复杂度主要衡量 一个算法所需要的额外空间
二、时间复杂度
1.1 概念
算法中的基本操作的执行次数,为算法的时间复杂度。
1.2 大O的渐进表示法
计算fun1基本操作执行了多少次?
void fun1(int N){
int count=0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
count++; //执行N*N次
}
}
for (int k = 0; k < 2*N; k++) {
count++; //执行2*N次
}
int M=10;
while ((M--)>0){
count++; //执行10次
}
}
分析得fun1 执行的基本操作次数 :
实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们
使用大O的渐进表示法。
那如何使用呢?
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
使用大O的渐进表示法以后,fun1的时间复杂度为:O(N^2)
我们发现,大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
但实际情况时间复杂度关注的是算法的最坏情况
1.3 时间复杂度计算举例
- 例1:
void fun2(int X,int Y){
int count=0;
for (int i = 0; i < X; i++) {
count++;
}
for (int j = 0; j < Y; j++) {
count++;
}
}
基本操作执行了X+Y次,有两个未知数X和Y,时间复杂度为 O(X+Y)
- 例2:
冒泡排序
void bubbleSort(int[] array){
for (int end = array.length; end > 0; end--) {
boolean flg=true;
for (int i = 1; i < end; i++) {
if (array[i-1]>array[i]){
Swap(array,i-1,i);
flg=false;
}
}
if (flg==true){
break;
}
}
}
基本操作执行最好N次,最坏执行了 (N*(N-1))/2 次,通过推导大O阶方法+时间复杂度一般看最坏,时间
复杂度为 O(N^2)
- 例3:
二分查找
int binarySearch(int[] array,int num){
int s=0;
int e= array.length-1;
while (s <= e){
int mid=s + ((e-s)/2);
if (array[mid]<num){
s=mid+1;
} else if (array[mid]>num) {
e=mid-1;
}else{
return mid;
}
return -1;
}
}
每次查找,范围都缩小一半,一共N个数字,查一次除一次2:N/2/2/2/2…/2=1
我们想求时间复杂度,就是想求得最多的查找的次数,设最多查找了x次,则可以得到:
N /(2^x)= 1
N = 2^x
则
时间复杂度为:O(log2N)
- 例4:
阶乘递归
long factorial(int N){ //阶乘递归的时间复杂度看递归的次数
return N < 2 ? N : factorial(N-1)*N;
}
基本操作递归了N次,时间复杂度为O(N)
- 例5:
斐波那契递归(多路递归)
int fibonacc(int N){
return N < 2 ? N : fibonacc(N-1)+fibonacc(N-2);
}
斐波那契函数的递归过程类似一棵二叉树
共有2^N个节点
一共递归了2^N-1次
得出最后时间复杂度为O(2^N)
三、空间复杂度
1.1 概念
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。
1.2 空间复杂度计算举例
- 例1:
冒泡排序
void bubbleSort(int[] array){
for (int end = array.length; end > 0; end--) {
boolean flg=true;
for (int i = 1; i < end; i++) {
if (array[i-1]>array[i]){
Swap(array,i-1,i);
flg=false;
}
}
if (flg==true){
break;
}
}
}
使用了常数个额外空间,所以空间复杂度为 O(1)
- 例2:
求斐契那波数列
int[] fibonacci(int n){
int[] fibArray=new int[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)
- 例3:
递归阶乘
long factorial(int N){
return N < 2 ? N : factorial(N-1)*N;
}
递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间。空间复杂度为O(N)