算法是特定問題求解步驟的描述ios
在計算機中表現爲指令的有限序列算法
算法是獨立存在的一種解決問題的方法和思想。數組
對於算法而言,語言並不重要,重要的是思想。緩存
數據結構只是靜態的描述了數據元素之間的關係數據結構
高效的程序須要在數據結構的基礎上設計和選擇算法函數
程序=數據結構+算法 學習
總結:測試
算法是爲了解決實際問題而設計的spa
數據結構是算法須要處理的問題載體設計
數據結構與算法相輔相成
輸入
算法具備0個或多個輸入
輸出
算法至少有1個或多個輸出
有窮性
算法在有限的步驟以後會自動結束而不會無限循環
肯定性
算法中的每一步都有肯定的含義,不會出現二義性
可行性
算法的每一步都是可行的
一、過後統計法
比較不一樣算法對同一組輸入數據的運行處理時間
缺陷
爲了得到不一樣算法的運行時間必須編寫相應程序
運行時間嚴重依賴硬件以及運行時的環境因素
算法的測試數據的選取至關困難
過後統計法雖然直觀,可是實施困難且缺陷多。
二、事前分析估算
依據統計的方法對算法效率進行估算
影響算法效率的主要因素
算法採用的策略和方法
問題的輸入規模
編譯器所產生的代碼
計算機執行速度
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> //算法最終編譯成具體的計算機指令 //每個指令,在具體的計算機上運行速度固定 //經過具體的n的步驟,就能夠推導出算法的複雜度 long sum1(int n) { long ret = 0; int* array = (int*)malloc(n * sizeof(int)); int i = 0; for(i=0; i<n; i++) { array[i] = i + 1; } for(i=0; i<n; i++) { ret += array[i]; } free(array); return ret; } long sum2(int n) { long ret = 0; int i = 0; for(i=1; i<=n; i++) { ret += i; } return ret; } long sum3(int n) { long ret = 0; if( n > 0 ) { ret = (1 + n) * n / 2; } return ret; } void mytest() { printf("%d\n", sum1(100)); printf("%d\n", sum2(100)); printf("%d\n", sum3(100)); return; } int main() { mytest(); system("pause"); return 0; }
int func(int a[], int len) { int i = 0; int j = 0; int s = 0; for(i=0; i<len; i++) n { for(j=0; j<len; j++) n { s += i*j; //n*n } } return s; } //n*n
注意1:判斷一個算法的效率時,每每只須要關注操做數量的最高次項,其它次要項和常數項能夠忽略。
注意2:在沒有特殊說明時,咱們所分析的算法的時間複雜度都是指最壞時間複雜度。
二、大O表示法
算法效率嚴重依賴於操做(Operation)數量
在判斷時首先關注操做數量的最高次項
操做數量的估算能夠做爲時間複雜度的估算
O(5) = O(1)
O(2n + 1) = O(2n) = O(n)
O(n2+ n + 1) = O(n2)
O(3n3+1) = O(3n3) = O(n3)
常見時間複雜度
關係
三、算法的空間複雜度
算法的空間複雜度經過計算算法的存儲空間實現
S(n) = O(f(n))
其中,n爲問題規模,f(n))爲在問題規模爲n時所佔用存儲空間的函數
大O表示法一樣適用於算法的空間複雜度
當算法執行時所須要的空間是常數時,空間複雜度爲O(1)
空間與時間的策略
多數狀況下,算法執行時所用的時間更使人關注
若是有必要,能夠經過增長空間複雜度來下降時間複雜度
同理,也能夠經過增長時間複雜度來下降空間複雜度
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> /* 問題: 在一個由天然數1-1000中某些數字所組成的數組中,每一個數字可能出現零次或者屢次。 設計一個算法,找出出現次數最多的數字。 */ // 方法1: 排序,而後找出出現次數最多的數字 // 排序,而後找出出現次數最多的數字 // 方法2: 把每一個數字出現的次數的中間結果,緩存下來;在緩存的結果中求最大值 void search(int a[], int len) { int sp[1000] = {0}; int i = 0; int max = 0; for (i = 0; i < len; i++) { int index = a[i] - 1; sp[index]++; } for (i = 0; i < 1000; i++) { if (max < sp[i]) { max = sp[i]; } } for (i = 0; i < 1000; i++) { if (max == sp[i]) { printf("%d\n", i + 1); } } } void mytest() { int array[] = {1, 1, 3, 4, 5, 6, 6, 6, 2, 3}; search(array, sizeof(array)/sizeof(array[0])); return; } int main() { mytest(); system("pause"); return 0; }
1 | 大部分程序的大部分指令之執行一次,或者最多幾回。若是一個程序的全部指令都具備這樣的性質,咱們說這個程序的執行時間是常數。 |
logN | 若是一個程序的運行時間是對數級的,則隨着N的增大程序會漸漸慢下來,若是一個程序將一個大的問題分解成一系列更小的問題,每一步都將問題的規 模縮減成幾分之一 ,通常就會出現這樣的運行時間函數。在咱們所關心的範圍內,能夠認爲運行時間小於一個大的常數。對數的基數會影響這個常數,但改變不會太 大:當N=1000時,若是基數是10,logN等於3;若是基數是2,logN約等於10.當N=1 00 000,logN只是前值的兩倍。當N時原來的兩倍,logN只增加了一個常數因子:僅當從N增加到N平方時,logN纔會增加到原來的兩倍。 |
N | 若是程序的運行時間的線性的,極可能是這樣的狀況:對每一個輸入的元素都作了少許的處理。當N=1 000 000時,運行時間大概也就是這個數值;當N增加到原來的兩倍時,運行時間大概也增加到原來的兩倍。若是一個算法必須處理N個輸入(或者產生N個輸出), 那麼這種狀況是最優的。 |
NlogN | 若是某個算法將問題分解成更小的子問題,獨立地解決各個子問題,最後將結果綜合起來 ,運行時間通常就是NlogN。咱們找不到一個更好的形容, 就暫且將這樣的算法運行時間叫作NlogN。當N=1 000 000時,NlogN大約是20 000 000。當N增加到原來的兩倍,運行時間超過原來的兩倍,但超過不是太多。 |
N平方 |
若是一個算法的運行時間是二次的(quadratic),那麼它通常只能用於一些規模較小的問題。這樣的運行時間一般存在於須要處理每一對輸入 數據項的算法(在程序中極可能表現爲一個嵌套循環)中,當N=1000時,運行時間是1 000 000;若是N增加到原來的兩倍,則運行時間將增加到原來的四倍。 |
N三次方 | 相似的,若是一個算法須要處理輸入數據想的三元組(極可能表現爲三重嵌套循環),其運行時間通常就是三次的,只能用於一些規模較小的問題。當N=100時,運行時間就是1 000 000;若是N增加到原來的兩倍,運行時間將會增加到原來的八倍。 |
2的N次方 | 若是一個算法的運行時間是指數級的(exponential),通常它很難在實踐中使用,即便這樣的算法一般是對問題的直接求解。當N=20時,運行時間是1 000 000;若是增加到原來的兩倍時,運行時間將是原時間的平方! |
log log N 能夠看做是一個常數:即便N不少,兩次去對數以後也會變得很小
之前光看了n多排序算法,知道僅經過比較的排序算法一共兩種複雜度
O(N2)或O(N*lgN),
因爲高數學的很差,以前一看到後者就放棄了思考,沒有真正研究爲何會有個lgN,
這兩天工做不是很忙,看了一下基礎知識,有了必定的認識,算是初步搞清楚了緣由.
寫在這裏算是一個記錄,若是有問題也請你們指正.
說到N*lgN的算法大體上有幾種:堆排序,歸併排序,快速排序.
因爲學習數據結構的時候老師講過快速排序,(其實各類都講過,我只記住了這種)我如今仍是很是有印象的,
這幾種排序實際都有一個共同點,這個共同點讓他們有了lgN的特性.
都是使用了分治算法,把大集合經過分治,造成小集合,小集合的排序再次遞歸更小集合,直到1個(還多是2個或3個)元素爲止.
這樣對於整個集合來說,每次遞歸都是處理2個元素數量是1/2當前元素數量的新集合,
f(x) = f(x/2) + a (有限極小數)
這個特色在堆排序和歸併排序中尤其突出,他們是絕對的遵循2分法的.而快速排序結果是隨機的,若是點背,可能出現O(N*N)的可能性
下面用堆排序爲例說明一下NlgN:
爲了讓更多人明白,我簡單把堆排序說一下:
堆排序就是把原來輸入的值串,當成一棵徹底2叉樹,每次找最大值的時候,都是把數的左右子節點比較,把大於本身的最大的一個跟本身互換,找到一個後,從新找剩下的n-1個,一直到最終找完全部節點.
因爲遞歸使用的是深度優先,每次都會從最底層往上找起,每次找的次數假設是F(x),則其須要找兩個子樹F(x/2)而且等兩個子樹處理完後,比較2次(子樹的根比較一次,跟本身比較一次)若是連移動都算上,是3次操做
值的注意的是,因爲最初排列過了之後,找子樹的時候只要找那顆被破壞了的子樹便可,
另外一顆排過序了的不須要再找了. (這個我本身都感受說的不明白,若是實在不行,你們再去看看相關資料吧)
這樣分析下來,找第x個節點的操做次數爲: f(x) <= f((x-1)/2) +3 (固然,我理解算成 + 2也行,x-1是把根去掉)
因爲當x爲2的整次方倍 + 1 的時候,正好是這個數值,當其餘的狀況 也不大於這個值
因此咱們能夠就使用最大值的狀況 f(x) = f((x-1)/2) + 3; 爲了計算更容易
直接 f(x) = f(x/2) + 3
f(x/2) = f(x/4) + 3
......
f(x/2m-1) = f(x/2m) + 3 (2m>=x) <= f(1)+3 < 3;
因爲一共m個算式 加起來是 f(x) = 3m 而 m = log(2)(x)
f(x)=3log(2)(x);
而計算f(1)+f(2) + ... + f(n)的時候,咱們把他分城 m段(m= log(2)(n)) (分別爲1,2,4,8,...2m-1)個元素(固然最後一端可能沒有那麼多)
求他們的和的話就是
2m-1(m-1) + 2m-2(m-2) + ....<2m-1m + 2m-2m + ... < 2mm 而m = log(2)(N)
2mm = 2log(2)(n)*log(2)(n) = N * log(2)(N)
得證 哈哈