事后統計方法
事后統計方法:這種方法主要是通過設計好的測試程序和數據,利用計算機計時器對不同算法編制的程序的運行時間進行比較,從而確定算法效率的高低。
但這種方法顯然是有很大缺陷的:
必須依據算法事先編制好程序,這通常需要花費大量的時間和精力。如果編制出來發現它根本是很糟糕的算法,不是竹籃打水一場空嗎?
事前分析估算方法
事前分析估算方法:在計算機程序編寫前,依據統計方法對算法進行估算。
經過分析,我們發現,一個用高級程序語言編寫的程序在計算機上運行時所消耗的時間取決于下列因素:
1.編譯產生的代碼質量。
2.算法采用的策略、方法。
3.問題的輸入規模。
4.機器執行指令的速度。
由此可見,拋開這些與計算機、軟件有關的因素,一個程序的運行時間依賴于算法的好壞和問題的輸入規模。
簡單算法比較
計算1+2+3+...+100的值
第一種算法:
int i, sum = 0,n = 100; /*執行1次*/
for(i = 1; i < = n; i++) /*執行了n+1次*/
{
sumsum = sum + i; /*執行n次*/
}
printf("%d", sum); /*執行1次*/
第二種算法:
int sum = 0,n = 100; /*執行1次*/
sum = (1 + n) * n/2; /*執行1次*/
printf("%d", sum); /*執行1次*/
- 第一種算法執行了1+(n+1)+n+1=2n+3次。
- 第二種算法執行了1+1+1=3次。
事實上兩個算法的第一條和最后一條語句是一樣的,所以我們關注的代碼其實是中間的那部分,我們把循環看作一個整體,忽略頭尾循環判斷的開銷,那么這兩個算法其實就是n次與1次的差距
為什么在這里我們要看成n次與1次的差距,而不是精確到2n+1 與1次的差距?
我們再來延伸一下上面這個例子:
int i, j, x = 0,sum = 0,n = 100; /*執行1次*/
for(i = 1; i < = n; i++)
{
for (j = 1; j < = n; j++)
{
x++; /*執行n×n次*/
sumsum = sum + x;
}
}
printf("%d", sum); /*執行1次*/
這個例子中,i從1到100,每次都要讓j循環100次,而當中的x++和sum = sum + x;其實就是1+2+3+…+10000,也就是1002次,所以這個算法當中,循環部分的代碼整體需要執行n2(忽略循環體頭尾的開銷)次。顯然這個算法的執行次數對于同樣的輸入規模n = 100,要多于前面兩種算法,這個算法的執行時間隨著n的增加也將遠遠多于前面兩個。
此時你會看到,測定運行時間最可靠的方法就是計算對運行時間有消耗的基本操作的執行次數。運行時間與這個計數成正比。
我們不關心編寫程序所用的程序設計語言是什么,也不關心這些程序將跑在什么樣的計算機中,我們只關心它所實現的算法。這樣,不計那些循環索引的遞增和循環終止條件、變量聲明、打印結果等操作,最終,在分析程序的運行時間時,最重要的是把程序看成是獨立于程序設計語言的算法或一系列步驟。
可以從問題描述中得到啟示,同樣問題的輸入規模是n,求和算法的第一種,求1+2+…+n需要一段代碼運行n次。那么這個問題的輸入規模使得操作數量是f(n) = n,顯然運行100次的同一段代碼規模是運算10次的10倍。而第二種,無論n為多少,運行次數都為1,即f(n) = 1;第三種,運算100次是運算10次的100倍。因為它是f(n) = n2。
我們在分析一個算法的運行時間時,重要的是把基本操作的數量與輸入規模關聯起來,即基本操作的數量必須表示成輸入規模的函數。
函數的漸近增長在說函數的漸近增長的例子前,先說說概念,
函數的漸近增長:給定兩個函數f(n)和g(n),如果存在一個整數N,使得對于所有的n > N,f(n)總是比g(n)大,那么,我們說f(n)的漸近增長快于g(n)。
文字說明,比較難理解,我們利用下面的表格來說明
注意:n^2代表n 的平方,n^3代表n的立方
數值\函數 | n | 2n | 2n+1 | 3n+8 | n^2 | 2n^2 | 2n^2+2n+1 | n^3 |
---|---|---|---|---|---|---|---|---|
1 | 1 | 2 | 3 | 11 | 1 | 2 | 5 | 1 |
2 | 2 | 4 | 5 | 14 | 4 | 8 | 13 | 8 |
3 | 3 | 6 | 7 | 17 | 9 | 18 | 25 | 27 |
5 | 5 | 10 | 11 | 23 | 25 | 50 | 61 | 125 |
9 | 9 | 18 | 19 | 35 | 81 | 162 | 181 | 729 |
10 | 10 | 20 | 21 | 38 | 100 | 200 | 221 | 1000 |
100 | 100 | 200 | 201 | 308 | 10000 | 20000 | 20201 | 1000000 |
1000 | 1000 | 2000 | 2001 | 3008 | 1000000 | 2000000 | 2002001 | 1000000000 |
10000 | 10000 | 20000 | 20001 | 30008 | 100000000 | 200000000 | 200020001 | 1000000000000 |
100000 | 100000 | 200000 | 200001 | 300008 | 10000000000 | 20000000000 | 20000200001 | 1000000000000000 |
例如:f(n)=2n^2+1,g(n)=2n+1
當n=1是f(n)=g(n),這個時候對應上面的概念,N=1,當n>N,也就是當n>1時,f(n)>g(n),所以,我們說f(n)的漸近增加快于g(n)
為了更好理解這些特點,我做了一些圖表,以便更加清楚的知道為什么
當輸入數值非常大的時候,兩條曲線基本重疊,或者可以說看作重疊,所以得出結論是,2n+1其中里面作為常數的1,在輸入數值大到一定程度,他對于函數的影響可以忽略不計,這時候的1就被看作是次要項
同樣,2n^2+2n+1
由于漸近增長是可以看作是一種抽象,所以他的對比具有一些特點:
1.注意關注函數最高次冪的變化
2.忽略次要項與乘數
特別感謝:
raylee2007的專欄