本節內容html
算法(Algorithm)是指解題方案的準確而完整的描述,是一系列解決問題的清晰指令,算法表明着用系統的方法描述解決問題的策略機制。也就是說,可以對必定規範的輸入,在有限時間內得到所要求的輸出。若是一個算法有缺陷,或不適合於某個問題,執行這個算法將不會解決這個問題。不一樣的算法可能用不一樣的時間、空間或效率來完成一樣的任務。一個算法的優劣能夠用空間複雜度與時間複雜度來衡量。算法
一個算法應該具備如下七個重要的特徵:數組
①有窮性(Finiteness):算法的有窮性是指算法必須能在執行有限個步驟以後終止;函數
②確切性(Definiteness):算法的每一步驟必須有確切的定義;測試
③輸入項(Input):一個算法有0個或多個輸入,以刻畫運算對象的初始狀況,所謂0個輸 入是指算法自己定出了初始條件;網站
④輸出項(Output):一個算法有一個或多個輸出,以反映對輸入數據加工後的結果。沒 有輸出的算法是毫無心義的;spa
⑤可行性(Effectiveness):算法中執行的任何計算步驟都是能夠被分解爲基本的可執行 的操做步,即每一個計算步均可以在有限時間內完成(也稱之爲有效性);code
⑥高效性(High efficiency):執行速度快,佔用資源少;htm
⑦健壯性(Robustness):對數據響應正確。對象
計算機科學中,算法的時間複雜度是一個函數,它定量描述了該算法的運行時間,時間複雜度經常使用大O符號(大O符號(Big O notation)是用於描述函數漸進行爲的數學符號。更確切地說,它是用另外一個(一般更簡單的)函數來描述一個函數數量級的漸近上界。在數學中,它通常用來刻畫被截斷的無窮級數尤爲是漸近級數的剩餘項;在計算機科學中,它在分析算法複雜性的方面很是有用。)表述,使用這種方式時,時間複雜度可被稱爲是漸近的,它考察當輸入值大小趨近無窮時的狀況。
大O,簡而言之能夠認爲它的含義是「order of」(大約是)。
無窮大漸近
大O符號在分析算法效率的時候很是有用。舉個例子,解決一個規模爲 n 的問題所花費的時間(或者所需步驟的數目)能夠被求得:T(n) = 4n^2 - 2n + 2。
當 n 增大時,n^2; 項將開始占主導地位,而其餘各項能夠被忽略——舉例說明:當 n = 500,4n^2; 項是 2n 項的1000倍大,所以在大多數場合下,省略後者對錶達式的值的影響將是能夠忽略不計的。
常數又稱定數,是指一個數值不變的常量,與之相反的是變量
爲何下面算法的時間複雜度不是O(3),而是O(1)。
1
2
3
|
int sum = 0,n = 100; /*執行一次*/
sum = (1+n)*n/2; /*執行一次*/
printf("%d", sum); /*行次*/
|
這個算法的運行次數函數是f(n)=3。根據咱們推導大O階的方法,第一步就是把常數項3改成1。在保留最高階項時發現,它根本沒有最高階項,因此這個算法的時間複雜度爲O(1)。
另外,咱們試想一下,若是這個算法當中的語句sum=(1+n)*n/2有10句,即:
1
2
3
4
5
6
7
8
9
10
11
12
|
int sum = 0, n = 100; /*執行1次*/
sum = (1+n)*n/2; /*執行第1次*/
sum = (1+n)*n/2; /*執行第2次*/
sum = (1+n)*n/2; /*執行第3次*/
sum = (1+n)*n/2; /*執行第4次*/
sum = (1+n)*n/2; /*執行第5次*/
sum = (1+n)*n/2; /*執行第6次*/
sum = (1+n)*n/2; /*執行第7次*/
sum = (1+n)*n/2; /*執行第8次*/
sum = (1+n)*n/2; /*執行第9次*/
sum = (1+n)*n/2; /*執行第10次*/
printf("%d",sum); /*執行1次*/
|
事實上不管n爲多少,上面的兩段代碼就是3次和12次執行的差別。這種與問題的大小無關(n的多少),執行時間恆定的算法,咱們稱之爲具備O(1)的時間複雜度,又叫常數階。
注意:無論這個常數是多少,咱們都記做O(1),而不能是O(3)、O(12)等其餘任何數字,這是初學者經常犯的錯誤。
O(n【時間規模,也能夠理解爲運行次數】),當運算裏沒有最高階項(也就是次方)時,時間規模就是1,因此爲o(1),
若是運算裏包含了最高階項(也就是次方)時,且次方數不爲1時,時間規模就是最高階項(也就是次方數),如o(3),
1.用常數1取代運行時間中的全部加法常數
2.在修改後的運行次數函數中,只保留最高階項
3.若是最高階項存在且不是1,則去除與這個項相乘的常數
對數
若是a的x次方等於N(a>0,且a不等於1),那麼數x叫作以a爲底N的對數(logarithm),記做x=logaN, 。其中,a叫作對數的底數,N叫作真數。
5^2 = 25 , 記做 2= log5 25
對數是一種運算,與指數是互逆的運算。例如
① 3^2=9 <==> 2=log<3>9;
② 4^(3/2)=8 <==> 3/2=log<4>8;
③ 10^n=35 <==> n=lg35。爲了使用方便,人們逐漸把以10爲底的經常使用對數記做lgN
對數階
1
2
3
4
5
6
7
8
9
|
int count = 1;
while (count < n)
{
count = count * 2; /* 時間複雜度爲O(1)的程序步驟序列 */
}
|
因爲每次count乘以2以後,就距離n更近了一分。
也就是說,有多少個2相乘後大於n,則會退出循環。
由2^x=n獲得x=log2n。因此這個循環的時間複雜度爲O(logn)。
執行時間隨問題規模增加呈正比例增加
1
2
3
4
5
|
data = [ 8,3,67,77,78,22,6,3,88,21,2]
find_num = 22
for i in data:
if i == 22:
print("find",find_num,i )
|
1
2
3
4
|
for i in range(100):
for k in range(100):
print(i,k)
|
立方階O(n^3)
k次方階O(n^k),
指數階O(2^n)。
隨着問題規模n的不斷增大,上述時間複雜度不斷增大,算法的執行效率越低。
定義:若是一個問題的規模是n,解這一問題的某一算法所須要的時間爲T(n),它是n的某一函數 T(n)稱爲這一算法的「時間複雜性」。 當輸入量n逐漸加大時,時間複雜性的極限情形稱爲算法的「漸近時間複雜性」。 咱們經常使用大O表示法表示時間複雜性,注意它是某一個算法的時間複雜性。大O表示只是說有上界,由定義若是f(n)=O(n),那顯然成立f(n)=O(n^2),它給你一個上界,但並非上確界,但人們在表示的時候通常都習慣表示前者。 此外,一個問題自己也有它的複雜性,若是某個算法的複雜性到達了這個問題複雜性的下界,那就稱這樣的算法是最佳算法。 「大O記法」:在這種描述中使用的基本參數是 n,即問題實例的規模,把複雜性或運行時間表達爲n的函數。這裏的「O」表示量級 (order),好比說「二分檢索是 O(logn)的」,也就是說它須要「經過logn量級的步驟去檢索一個規模爲n的數組」記法 O ( f(n) )表示當 n增大時,運行時間至多將以正比於 f(n)的速度增加。 這種漸進估計對算法的理論分析和大體比較是很是有價值的,但在實踐中細節也可能形成差別。例如,一個低附加代價的O(n2)算法在n較小的狀況下可能比一個高附加代價的 O(nlogn)算法運行得更快。固然,隨着n足夠大之後,具備較慢上升函數的算法必然工做得更快。 O(1) Temp=i;i=j;j=temp; 以上三條單個語句的頻度均爲1,該程序段的執行時間是一個與問題規模n無關的常數。算法的時間複雜度爲常數階,記做T(n)=O(1)。若是算法的執行時間不隨着問題規模n的增長而增加,即便算法中有上千條語句,其執行時間也不過是一個較大的常數。此類算法的時間複雜度是O(1)。 O(n^2) 2.1. 交換i和j的內容 sum=0; (一次) for(i=1;i<=n;i++) (n次 ) for(j=1;j<=n;j++) (n^2次 ) sum++; (n^2次 ) 解:T(n)=2n^2+n+1 =O(n^2) 2.2. for (i=1;i<n;i++) { y=y+1; ① for (j=0;j<=(2*n);j++) x++; ② } 解: 語句1的頻度是n-1 語句2的頻度是(n-1)*(2n+1)=2n^2-n-1 f(n)=2n^2-n-1+(n-1)=2n^2-2 該程序的時間複雜度T(n)=O(n^2). O(n) 2.3. a=0; b=1; ① for (i=1;i<=n;i++) ② { s=a+b; ③ b=a; ④ a=s; ⑤ } 解:語句1的頻度:2, 語句2的頻度: n, 語句3的頻度: n-1, 語句4的頻度:n-1, 語句5的頻度:n-1, T(n)=2+n+3(n-1)=4n-1=O(n). O(log2n )2.4. i=1; ① while (i<=n) i=i*2; ②解: 語句1的頻度是1, 設語句2的頻度是f(n), 則:2^f(n)<=n;f(n)<=log2n 取最大值f(n)= log2n, T(n)=O(log2n )O(n^3)2.5. for(i=0;i<n;i++) { for(j=0;j<i;j++) { for(k=0;k<j;k++) x=x+2; } }解:當i=m, j=k的時候,內層循環的次數爲k當i=m時, j 能夠取 0,1,...,m-1 , 因此這裏最內循環共進行了0+1+...+m-1=(m-1)m/2次因此,i從0取到n, 則循環共進行了: 0+(1-1)*1/2+...+(n-1)n/2=n(n+1)(n-1)/6因此時間複雜度爲O(n^3). 咱們還應該區分算法的最壞狀況的行爲和指望行爲。如快速排序的最 壞狀況運行時間是 O(n^2),但指望時間是 O(nlogn)。經過每次都仔細 地選擇基準值,咱們有可能把平方狀況 (即O(n^2)狀況)的機率減少到幾乎等於 0。在實際中,精心實現的快速排序通常都能以 (O(nlogn)時間運行。下面是一些經常使用的記法:訪問數組中的元素是常數時間操做,或說O(1)操做。一個算法如 果能在每一個步驟去掉一半數據元素,如二分檢索,一般它就取 O(logn)時間。用strcmp比較兩個具備n個字符的串須要O(n)時間。常規的矩陣乘算法是O(n^3),由於算出每一個元素都須要將n對 元素相乘並加到一塊兒,全部元素的個數是n^2。指數時間算法一般來源於須要求出全部可能結果。例如,n個元 素的集合共有2n個子集,因此要求出全部子集的算法將是O(2n)的。指數算法通常說來是太複雜了,除非n的值很是小,由於,在 這個問題中增長一個元素就致使運行時間加倍。不幸的是,確實有許多問題 (如著名的「巡迴售貨員問題」 ),到目前爲止找到的算法都是指數的。若是咱們真的遇到這種狀況,一般應該用尋找近似最佳結果的算法替代之。 |