一、時間複雜度分析
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,在這些排序算法中每個節點處理的數據規模是逐漸縮小的,而上一個例子每個節點處理的數據規模是一樣的。