算法的時間複雜度和空間複雜度計算【轉載】
1.算法的時間複雜度定義:
在進行算法分析時,語句總的執行次數是關於問題規模的函數,可以通過分析隨的變化情況來確定的數量級。
算法的時間複雜度,也就是算法的時間量度,記作:,它表示隨問題規模n的增大,算法執行時間的增長率和的增長率相同,其中是問題規模的某個函數,用來體現算法時間複雜度的記法,我們稱之爲大記法。
2.推導大階方法:
(1)用常數1取代運行時間中的所有加法常數;
(2)在修改後的運行次數函數中,只保留最高階項;
(3)如果最高階項存在且不是1,則去除與這個項目相乘的常數。得到的結果就是大O階。
3.推導示例:
(1)常數階:
以下代碼利用高斯定理計算1,2,……n個數的和,是順序結構的時間複雜度。
int sum = 0, n = 100; /*執行一次*/
sum = (1 + n) * n / 2; /*執行一次*/
printf("%d",sum); /*執行一次*/
這個算法的運行次數函數是,根據推導大階的方法,第一步是把常數項3改爲1,在保留最高階項時發現它沒有最高階項,所以這個算法的時間複雜度爲(1)。
另外,如果這個算法當中的語句 “sum = (1+n)*n/2;” 有10 句,則示例給出的代碼就是3次和12次的差異,這種與問題的大小(n)無關、執行時間恆定的算法,我們稱之爲具有(1)的時間複雜度,又叫常數階。
對於分支結構而言,無論是真或假執行的次數都是恆定的,不會隨着n 的變大而發生變化,所以單純的分支結構(不包含在循環結構中)其時間複雜度也是(1)。
(2)線性階:
線性階的循環結構會複雜很多,要確定某個算法的階次常常需要確定某個特定語句或某個語句集運行的次數。因此要分析算法的複雜度關鍵是要分析循環結構的運行情況。
下面這段代碼的循環時間複雜度爲O(n), 因爲循環體中的代碼須要執行n次。
int i;
for(i = 0; i < n; i++){
/*時間複雜度爲O(1)的程序步驟序列*/
}
(3)對數階:
int count = 1;
while (count < n){
count = count * 2;
/*時間複雜度爲O(1)的程序步驟序列*/
}
while循環每執行一次count乘以2,count就距離n更近一些, 當若干個2相乘後大於n,則會退出while循環。
由2^x=n 得到x=logn, 所以這個循環的時間複雜度爲O(logn)。
(4)平方階:
下面代碼是一個循環嵌套,它的內循環時間複雜度如上文所示爲O(n)。
int i, j;
for(i = 0; i < n; i++){
for(j = 0; j < n; j++){
/*時間複雜度爲O(1)的程序步驟序列*/
}
}
而對於外層的循環,不過是內部這個時間複雜度爲O(n)的語句,再循環n次,所以這段代碼的時間複雜度爲O(n^2)。如果外循環的循環次數改爲了m,時間複雜度就變爲O(mXn)。可以總結得出,循環的時間複雜度等於循環體的複雜度乘以該循環運行的次數。
下面代碼中的循環嵌套,由於當i=0時,內循環執行了n次,當i = 1時,執行了n-1次,……當i=n-1時,執行了1次。所以總的執行次數爲:
用推導大階的方法,第一條,沒有加法常數不予考慮;第二條,只保留最高階項,因此保留時(n^2)/2; 第三條,去除這個項相乘的常數,也就是去除1/2,最終這段代碼的時間複雜度爲(n2)。
int i, j;
for(i = 0; i < n; i++){
for(j = i; j < n; j++){ /*注意j = i而不是0*/
/*時間複雜度爲O(1)的程序步驟序列*/
}
}
從上例可知理解大推導的難點是對數列的一些相關運算。
(5)立方階:
下面代碼是一個三重循環嵌套,循環了(1^2+2^2+3^2+……+n^2) = n(n+1)(2n+1)/6次,按照上述大階推導方法,時間複雜度爲O(n^3)。
int i, j;
for(i = 1; i < n; i++)
for(j = 1; j < n; j++)
for(j = 1; j < n; j++){
/*時間複雜度爲O(1)的程序步驟序列*/
}
4.推導示例:
常見的時問複雜度如下表所示:
常用的時間複雜度所耗費的時間從小到大依次是:
上面提到O(1)常數階、O(logn)對數階、O(n)線性階、 O(n^2)平方階等,像O(n^3),過大的n都會使得結果變得不現實,同樣指數階O(2^n)和階乘階O(n!)等除非是很小的n值,否則哪怕n 只是100,都會使運行時間大幅增長,所以這種不切實際的算法時間複雜度一般都不去討論。
5.最壞情況與平均情況:
查找一個有n 個隨機數字數組中的某個數字,最好的情況是數組中第一個數字就是,那麼算法的時間複雜度爲O(1),但也有可能這個數字在數組的最後一個位置,那麼算法的時間複雜度就是O(n),這是最壞的情況。
最壞情況運行時間是一種保證,是運行時間的底線。 在應用中,這是一種最重要的需求, 通常除非特別指定, 我們提到的運行時間都是最壞情況的運行時間。
而平均運行時間是從概率的角度計算, 這個數字在每一個位置的可能性是相同的,所以發現目標元素的平均查找時間爲n/2次。平均運行時間是所有情況中最有意義的,因爲它是期望的運行時間,也就是當運行一段程序代碼時是希望看到平均運行時間的。
但是在現實中,平均運行時間很難通過分析得到,一般都是通過運行一定數量的實驗數據後估算出來的,因此一般在沒有特殊說明的情況下,時間複雜度都是指最壞的時間複雜度。
6.算法空間複雜度:
寫代碼時完全可以用空間來換取時間,舉例判斷某年是不是閏年的算法,每次給一個年份都是要通過計算得到是否是閏年的結果。 另一個辦法是事先建立一個有2050個元素的數組(年數略比現實多一點),然後把所有的年份按下標的數字對應,如果是閏年,此數組項的值就是1,如果不是值爲0。這樣,所謂的判斷某一年是否是閏年,就變成了查找這個數組的某一項的值是多少的問題。
此時運算量達到了最小化,但是硬盤上或者內存中需要存儲這2050個0和1,這是通過空間上的開銷來換取計算時間的小技巧。
算法的空間複雜度通過計算算法所需的存儲空間實現,算法空間複雜度的計算公式記作:S(n)= O(f(n)),其中,n爲問題的規模,f(n)爲語句關於n所佔存儲空間的函數。一般情況下,一個程序在機器上執行時,除了需要存儲程序本身的指令、常數、變量和輸入數據外,還需要存儲對數據操作的存儲單元,若輸入數據所佔空間只取決於問題本身,和算法無關,這樣只需要分析該算法在實現時所需的輔助單元即可。若算法執行時所需的輔助空間相對於輸入數據量而言是個常數,則稱此算法爲原地工作,空間複雜度爲O(1)。
通常使用"時間複雜度"來指運行時間的需求,使用"空間複雜度"指空間需求,當不用限定詞地使用"複雜度'時,通常都是指時間複雜度。
7.一些計算的規則:
(1)加法規則:
(2)乘法規則:
(3)一個經驗:
複雜度與時間效率的關係:
c(常數) < logn < n < n*logn < n^2 < n^3 < 2^n < 3^n < n!
l------------------------------l--------------------------l--------------l
較好 一般 較差
8.常用算法的時間複雜度和空間複雜度:
版權聲明:本文爲CSDN博主「鍵盤上的鋼琴師_v5」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/daijin888888/article/details/66970902