1.算法的效率算法
雖然計算機能快速的完成運算處理,但實際上,它也須要根據輸入數據的大小和算法效率來消耗必定的處理器資源。要想編寫出能高效運行的程序,咱們就須要考慮到算法的效率。
算法的效率主要由如下兩個複雜度來評估:
時間複雜度:評估執行程序所需的時間。能夠估算出程序對處理器的使用程度。
空間複雜度:評估執行程序所需的存儲空間。能夠估算出程序對計算機內存的使用程度。編程
設計算法時,通常是要先考慮系統環境,而後權衡時間複雜度和空間複雜度,選取一個平衡點。不過,時間複雜度要比空間複雜度更容易產生問題,所以算法研究的主要也是時間複雜度,不特別說明的狀況下,複雜度就是指時間複雜度。函數
時間頻度
一個算法執行所耗費的時間,從理論上是不能算出來的,必須上機運行測試才能知道。但咱們不可能也沒有必要對每一個算法都上機測試,只需知道哪一個算法花費的時間多,哪一個算法花費的時間少就能夠了。而且一個算法花費的時間與算法中語句的執行次數成正比例,哪一個算法中語句執行次數多,它花費時間就多。一個算法中的語句執行次數稱爲語句頻度或時間頻度。記爲T(n)。測試
時間複雜度
前面提到的時間頻度T(n)中,n稱爲問題的規模,當n不斷變化時,時間頻度T(n)也會不斷變化。但有時咱們想知道它變化時呈現什麼規律,爲此咱們引入時間複雜度的概念。通常狀況下,算法中基本操做重複執行的次數是問題規模n的某個函數,用T(n)表示,如有某個輔助函數f(n),使得當n趨近於無窮大時,T(n)/f(n)的極限值爲不等於零的常數,則稱f(n)是T(n)的同數量級函數,記做T(n)=O(f(n)),它稱爲算法的漸進時間複雜度,簡稱時間複雜度。spa
像前面用O( )來體現算法時間複雜度的記法,咱們稱之爲大O表示法。
算法複雜度能夠從最理想狀況、平均狀況和最壞狀況三個角度來評估,因爲平均狀況大多和最壞狀況持平,並且評估最壞狀況也能夠避免後顧之憂,所以通常狀況下,咱們設計算法時都要直接估算最壞狀況的複雜度。
大O表示法O(f(n)中的f(n)的值能夠爲一、n、logn、n²等,所以咱們能夠將O(1)、O(n)、O(logn)、O(n²)分別能夠稱爲常數階、線性階、對數階和平方階,那麼如何推導出f(n)的值呢?咱們接着來看推導大O階的方法。設計
推導大O階
推導大O階,咱們能夠按照以下的規則來進行推導,獲得的結果就是大O表示法:
1.用常數1來取代運行時間中全部加法常數。
2.修改後的運行次數函數中,只保留最高階項
3.若是最高階項存在且不是1,則去除與這個項相乘的常數。code
常數階
先舉了例子,以下所示。圖片
int sum = 0,n = 100; //執行一次 sum = (1+n)*n/2; //執行一次 System.out.println (sum); //執行一次
上面算法的運行的次數的函數爲f(n)=3,根據推導大O階的規則1,咱們須要將常數3改成1,則這個算法的時間複雜度爲O(1)。若是sum = (1+n)*n/2這條語句再執行10遍,由於這與問題大小n的值並無關係,因此這個算法的時間複雜度仍舊是O(1),咱們能夠稱之爲常數階。內存
線性階
線性階主要要分析循環結構的運行狀況,以下所示。資源
for(int i=0;i<n;i++){ //時間複雜度爲O(1)的算法 ... }
上面算法循環體中的代碼執行了n次,所以時間複雜度爲O(n)。
對數階
接着看以下代碼:
int number=1; while(number<n){ number=number*2; //時間複雜度爲O(1)的算法 ... }
能夠看出上面的代碼,隨着number每次乘以2後,都會愈來愈接近n,當number不小於n時就會退出循環。假設循環的次數爲X,則由2^x=n得出x=log₂n,所以得出這個算法的時間複雜度爲O(logn)。
平方階
下面的代碼是循環嵌套:
for(int i=0;i<n;i++){ for(int j=0;j<n;i++){ //複雜度爲O(1)的算法 ... } }
內層循環的時間複雜度在講到線性階時就已經得知是O(n),如今通過外層循環n次,那麼這段算法的時間複雜度則爲O(n²)。
接下來咱們來算一下下面算法的時間複雜度:
for(int i=0;i<n;i++){ for(int j=i;j<n;i++){ //複雜度爲O(1)的算法 ... } }
須要注意的是內循環中int j=i,而不是int j=0。當i=0時,內循環執行了n次;i=1時內循環執行了n-1次,當i=n-1時執行了1次,咱們能夠推算出總的執行次數爲:
n+(n-1)+(n-2)+(n-3)+……+1
=(n+1)+[(n-1)+2]+[(n-2)+3]+[(n-3)+4]+……
=(n+1)+(n+1)+(n+1)+(n+1)+……
=(n+1)n/2
=n(n+1)/2
=n²/2+n/2
根據此前講過的推導大O階的規則的第二條:只保留最高階,所以保留n²/2。根據第三條去掉和這個項的常數,則去掉1/2,最終這段代碼的時間複雜度爲O(n²)。
其餘常見覆雜度
除了常數階、線性階、平方階、對數階,還有以下時間複雜度:
f(n)=nlogn時,時間複雜度爲O(nlogn),能夠稱爲nlogn階。
f(n)=n³時,時間複雜度爲O(n³),能夠稱爲立方階。
f(n)=2ⁿ時,時間複雜度爲O(2ⁿ),能夠稱爲指數階。
f(n)=n!時,時間複雜度爲O(n!),能夠稱爲階乘階。
f(n)=(√n時,時間複雜度爲O(√n),能夠稱爲平方根階。
下面將算法中常見的f(n)值根據幾種典型的數量級來列成一張表,根據這種表,咱們來看看各類算法複雜度的差別。
n | logn | √n | nlogn | n² | 2ⁿ | n! |
---|---|---|---|---|---|---|
5 | 2 | 2 | 10 | 25 | 32 | 120 |
10 | 3 | 3 | 30 | 100 | 1024 | 3628800 |
50 | 5 | 7 | 250 | 2500 | 約10^15 | 約3.0*10^64 |
100 | 6 | 10 | 600 | 10000 | 約10^30 | 約9.3*10^157 |
1000 | 9 | 31 | 9000 | 1000 000 | 約10^300 | 約4.0*10^2567 |
從上表能夠看出,O(n)、O(logn)、O(√n )、O(nlogn )隨着n的增長,複雜度提高不大,所以這些複雜度屬於效率高的算法,反觀O(2ⁿ)和O(n!)當n增長到50時,複雜度就突破十位數了,這種效率極差的複雜度最好不要出如今程序中,所以在動手編程時要評估所寫算法的最壞狀況的複雜度。
下面給出一個更加直觀的圖:
其中x軸表明n值,y軸表明T(n)值(時間複雜度)。T(n)值隨着n的值的變化而變化,其中能夠看出O(n!)和O(2ⁿ)隨着n值的增大,它們的T(n)值上升幅度很是大,而O(logn)、O(n)、O(nlogn)隨着n值的增大,T(n)值上升幅度則很小。
經常使用的時間複雜度按照耗費的時間從小到大依次是:
O(1)<O(logn)<O(n)<O(nlogn)<O(n²)<O(n³)<O(2ⁿ)<O(n!)