一、时间复杂度分析
1、什么是大O
- O(f(n))表示运行算法所需要执行的指令数,和f(n)成正比
- 二分查找法O(longn) 所需执行指令数: a*longn
- 寻找数组中最大/最小值O(n) 所需执行指令数 :b*n
- 归并排序O(nlogn) 所需指令数:c*nlogn
- 选择排序O(n^2) 所需指令数:d*n^21
从图中可以看出来n的差距是量级上的差距,可以看出来前面的常数可以忽略不计
不同复杂度的函数
- 在学术上来讲O(f(n))表示算法执行的上界
归并排序复杂度是O(nlogn)的,但同时也是O(n^2)的,因为O表示时间的上界,这个算法真正执行的指令数可能是cnlogn 可能<= an^2的
但是业界就用O表示算法的执行最低上界
一般不会说归并排序是O(n^2)的 - 一个算法有多个部分组成,复杂度取最大的(前提是对应的n数量级是差不多的)
- O(nlogn+n)=O(nlogn)
- O(nlogn+n2)=O(n2)
如果O(AlongA+B) ,O(AlogA+B^2),这样的就不行
二、对数据规模的概念
1、如果想在1s内解决问题:
O(n2)的算法可以处理大约104级别的数据
O(n)的算法可以处理大约10^8级别的数据
O(nlogn) 的算法可以处理大约10^7级别的数据
2、空间复杂度
多开一个辅助数组:O(n)
多开一个辅助二维数组:O(n^2)
多开常数空间:O(1)
递归调用是有空间代价的
三、常见的复杂度分析
- O(1)
void swap(int &a ,int &b){
int temp=a;
a=b;
b=temp;
}
- O(n),典型的特征是存在一个循环,循环次数是c*n次
//反转字符串
void reverse(string &s){
int n=s.size();
for(int i=0;i<n/2;i++)
swap(s[i],s[n-1-i]);
}
- O(n^2),双重循环
- O(logn)
//二分查找
int binarySearch(int arr[],int n,int target){
int l=0,r=n-1;
while(l<=r){
int mid=l+(r-l)/2;
if(arr[mid]==target)
return mid;
if(arr[mid]>target){
r=mid-1;
}else{
l=mid+1;
}
}
return -1;
}
- O(sqrt(n))
//判断是不是质数
bool isPrime(int n){
for(int x=2;x*x<=n;x++){
if(n%x==0)
return false;
return true;
}
四、递归调用复杂度
//递归的二分查找
int binarySearch(int arr[],int l,int r,int traget){
if(l>r)
return -1;
int mid=l+(r-l)/2;
if(arr[mid]==target){
return mid;
}else if(arr[mid]>target){
return binarySearch(arr,l,mid-1,target);
}else{
return binarySearch(arr,mid+1,r,target);
}
}
- 这个算法递归的深度为O(logn),处理问题的复杂度是O(1),所以时间复杂度也是O(logn)
- 递归的时间复杂度:如果递归函数中,只进行一次递归调用,递归深度为depth,在每个递归函数中,时间复杂度为T,则总体时间复杂度为O(T*depth)
-
如果2次递归调用,如下面函数,分析复杂度应该计算调用次数
-
归并,快速排序等事件复杂度不是O(2^n)而是O(nlogn),因为再上一个例子中树的深度是n,而在这些排序算法中树的深度是logn,在这些排序算法中每个节点处理的数据规模是逐渐缩小的,而上一个例子每个节点处理的数据规模是一样的。