我們經常看到一些人說別人的程序如何如何的好或者是如何如何的差,那麼到底什麼樣的程序就算好呢,有沒有什麼標準呢,今天就說說如何去評價一個程序!!!
1. 任何一個程序都有空間和時間方面的問題,如何處理好這兩者之間的關係是關係程序好壞的一個重要方面.
所謂程序性能( program performance),是指運行一個程序所需要的內存大小和時間。
可以採用兩種方法來確定一個程序的性能,一個是分析的方法,一個是實驗的方法。在進行
性能分析( performance analysis )時,採用分析的方法,而在進行性能測量( p e r f o r m a n c e
m e a s u r e m e n t)時,藉助於實驗的方法。
程序的空間複雜性(space complexity)是指運行完一個程序所需要的內存大小。對一個程
序的空間複雜性感興趣的主要原因如下:
• 如果程序將要運行在一個多用戶計算機系統中,可能需要指明分配給該程序的內存大小。
• 對任何一個計算機系統,想提前知道是否有足夠可用的內存來運行該程序。
• 一個問題可能有若干個內存需求各不相同的解決方案。比如,對於你的計算機來說,某
個C + +編譯器僅需要1 M B的空間,而另一個C + +編譯器可能需要4 M B的空間。如果你的計算機
中內存少於4 M B,你只能選擇1 M B的編譯器。如果較小編譯器的性能比得上較大的編譯器,即
使用戶的計算機中有額外的內存,也寧願使用較小的編譯器。
• 可以利用空間複雜性來估算一個程序所能解決的問題的最大規模。例如,有一個電路模
擬程序,用它模擬一個有c個元件、w個連線的電路需要2 8 0 K + 1 0 *(c+w)字節的內存。如果
可利用的內存總量爲6 4 0 K字節,那麼最大可以模擬c+w≤3 6 K的電路。
程序的時間複雜性( time complexity)是指運行完該程序所需要的時間。對一個程序的時
間複雜性感興趣的主要原因如下:
• 有些計算機需要用戶提供程序運行時間的上限,一旦達到這個上限,程序將被強制結束。
一種簡易的辦法是簡單地指定時間上限爲幾千年。然而這種辦法可能會造成嚴重的財政問題,
因爲如果由於數據問題導致你的程序進入一個死循環,你可能需要爲你所使用的機時付出鉅額
資金。因此我們希望能提供一個稍大於所期望運行時間的時間上
正在開發的程序可能需要提供一個滿意的實時響應。例如,所有交互式程序都必須提
供實時響應。一個需要1分鐘才能把光標上移一頁或下移一頁的文本編輯器不可能被衆多的
用戶接受;一個電子表格程序需要花費幾分鐘才能對一個表單中的單元進行重新計值,那
麼只有非常耐心的用戶纔會樂意使用它;如果一個數據庫管理系統在對一個關係進行排序
時,用戶可以有時間去喝兩杯咖啡,那麼它也很難被用戶接受。爲交互式應用所設計的程
序必須提供滿意的實時響應。根據程序或程序模塊的時間複雜性,可以決定其響應時間是
否可以接受,如果不能接受,要麼重新設計正在使用的算法,要麼爲用戶提供一臺更快的
計算機。
• 如果有多種可選的方案來解決一個問題,那麼具體決定採用哪一個主要基於這些方案之
間的性能差異。對於各種解決方案的時間及空間複雜性將採用加權的方式進行評價。
2.空間複雜性
程序所需要的空間主要由以下部分構成:
• 指令空間(instruction space) 指令空間是指用來存儲經過編譯之後的程序指令所需的
空間。
• 數據空間( data space) 數據空間是指用來存儲所有常量和所有變量值所需的空間。數
據空間由兩個部分構成:
1) 存儲常量和簡單變量所需要的空間。
2) 存儲複合變量所需要的空間。這一類空間包括數據結構所需的空間及動態分配的空間。
• 環境棧空間(environment stack space) 環境棧用來保存函數調用返回時恢復運行所需要的信息。例如,如果函數f u n 1調用了函數f u n 2,那麼至少必須保存f u n 2結束時f u n 1將要繼續執行的指令的地址。
一個程序所需要的空間可以分成兩部分:
• 固定部分,它獨立於實例的特徵。一般來說,這一部分包含指令空間(即代碼空間)、簡
單變量及定長複合變量所佔用空間、常量所佔用空間等等。
• 可變部分,它由以下部分構成:複合變量所需的空間(這些變量的大小依賴於所解決的
具體問題),動態分配的空間(這種空間一般都依賴於實例的特徵),以及遞歸棧所需的空間
(該空間也依賴於實例的特徵)。
任意程序P 所需要的空間S(P)可以表示爲:
S (P) = c + Sp(實例特徵)
其中c 是一個常量,表示固定部分所需要的空間, Sp 表示可變部分所需要的空間。一個精確的分析還應當包括在編譯期間所產生的臨時變量所需要的空間(如圖2 - 1所示),這種空間是與編譯器直接相關的,除依賴於遞歸函數外,它還依賴於實例的特徵。在分析程序的空間複雜性時,我們將把注意力集中在估算Sp(實例特徵)上。對於任意給定的問題,首先需要確定實例的特徵以便於估算空間需求。實例特徵的選擇是一個很具體的問題,我們將求助於介紹各種可能性的實際例子。一般來說,我們的選擇受到相關數的數量以及程序輸入和輸出的規模的限制。有時還會使用更復雜的估算數據,這些數據來自於數據項之間的相互關係
實例分析:
template<class T>
T Abc(T& a, T& b, T& c)
{
return a+b+b*c+(a+b-c)/(a+b)+4;
}
在估算Sp 之前,必須選擇分析所使用的實例特徵。兩種可能性是:
( 1 )數據類型T;(2) a, b 和c 的大小。
假定使用T作爲實例特徵。由於a, b和c是引用參數,所以在函數中不需要爲它們的值分配空間,但是必須保存指向這些參數的指針。如果每個指針需要4個字節,那麼共需要12個字節的指針空間。因此函數所需要的總空間是一個常量,SA b c (實例特徵) = 0。
如果函數Abc 的參數是傳值參數,那麼每個參數需要分配大小爲sizeof (T) 的空間。在本例中,a, b 和c 所需要的空間爲3*sizeof (T) 。所需要的其他空間都獨立於T。因此SA b c (實例特徵) =3*sizeof (T)。
如果使用a, b 和c 的大小作爲實例特徵,則不管使用引用參數還是使用傳值
參數,都有SA b c (實例特徵) = 0。注意在傳值參數的情形下,分配給每個a , b和c的空間均爲s i z e o f ( T ),而不考慮存儲在這些變量中的實際值是多大。例如,如果T是d o u b l e類型,那麼每個字節將被分配8個字節的空間。
說明一下計算空間複雜度的一般方法:
計算程序佔的總的空間的大小,如果與想要的變量沒有關係,那麼S(變量)=0;
3.時間複雜性
一個程序P所佔用的時間T (P)=編譯時間+運行時間。編譯時間與實例的特徵無關。另外,
可以假定一個編譯過的程序可以運行若干次而不需要重新編譯。因此我們將主要關注程序的運
行時間。運行時間通常用“t p (實例特徵)”來表示。
分析時間的複雜性比較麻煩的,一般用兩個方法來估算運行時間:
(1)找出一個或多個關鍵操作,確定這些關鍵操作所需要的執行時間;
(2).確定程序總的執行步數
令P 是一個程序。假定把操作計數OP (n1, n2, ..., nk ) 視爲實例特徵n1, n2, ..., nk 的函數。對於任意程序實例I, 令o p e r a t i o nP (I) 爲該實例的操作計數,令S(n1, n2, ..., nk ) 爲集合{I |I 具有特徵n1, n2, ..., nk }。則P最好的操作計數爲:
OPB C (n1, n2 ..., nk ) = m i n { o p e r a t i on P (I ) | I∈S(n1, n2, ..., nk ) }
P最壞的操作計數爲:
OPW C (n1, n2 ..., nk ) =m a x { o p e r a t i o nP (I ) | I∈S(n1, n2, ..., nk ) }
4.符號描述程序的時間和空間複雜性
(1).大寫O符號
大寫O 符號給出了函數f的一個上限。
定義[大寫O符號] f (n)=O (g (n)) 當且僅當存在正的常數c 和n0,使得對於所有的n≥n0 , 有
f (n) ≤c g(n)。
上述定義表明,函數f 頂多是函數g 的c 倍,除非n 小於n0 。因此對於足夠大的n (如n≥n0 ),
g 是f 的一個上限(不考慮常數因子c)。在爲函數f 提供一個上限函數g 時,通常使用比較簡單的函數形式。比較典型的形式是含有n 的單個項(帶一個常數係數)。列出了一些常用的g 函數及其名稱。對於圖中的對數函數l o g n,沒有給出對數基,原因是對於任何大於1的常數a 和b 都有l o ga n = logb n / l o gb a, 所以l o ga n 和l o gb n 都有一個相對的乘法系數1/ l o gb a ,其中a是一個常量。
示例:
[線性函數] 考察f (n) = 3n+ 2。當n≥2時,3n+ 2≤3n+n=4n,所以f (n)=O (n),f (n)是一
個線性變化的函數;
[平方函數] 假定f (n) = 10n2 + 4n + 2 。對於n≥2, 有f (n)≤1 0n2 + 5 n。由於當n≥5時有
5n≤n2 ,因此對於n≥n0 = 5,f (n) ≤1 0n2 + n2 = 11n2 ,所以f (n)= O (n2 )。
[指數函數] 考察一個具有指數複雜性的例子f (n)= 6 * 2n + n2 。可以觀察到對於n≥4,
有n2 ≤2n,所以對於n≥4,有f (n)≤ 6 * 2n + 2n = 7 * 2 n ,因此6 * 2n+ n2 = O (2n )
[常數函數] 當f (n)是一個常數時,比如f (n) = 9或f (n) = 2 0 3 3,可以記爲f (n)=O (1)。這種寫法的正確性很容易證明。例如,對於f (n) = 9≤9 * 1,只要令c=9 以及n0 =0 即可得f (n)=O (1)。同樣地,對於f (n) = 2033≤2 0 3 3 * 1,只要令c= 2 0 3 3以及n0 = 0即可;
注意f (n) = O (g (n)) 並不等價於O (g (n)) = f (n)。實際上,O (g (n)) = f (n) 是無意義的。在這裏,使用符號=是不確切的,因爲=通常表示相等關係。可以通過把符號=讀作“是”而不是“等於”來避免這種矛盾。
爲了方便大家得到g(n),給出下面幾個定理:
定理2-2 [大O比率定理] 對於函數f (n)和g (n),若limn→∞f (n) / g (n) 存在,則f (n)=O (g (n) )當且僅存在確定的常數c,有limn→∞f (n) /g(n)≤c。
證明如果f (n)=O (g (n) ),則存在c> 0及某個n0,使得對於所有的n≥n0,有f (n) /g(n)≤c,因此limn→∞f (n) /g(n)≤c。接下來假定limn→∞f (n) /g(n)≤c,它表明存在一個n0,使得對於所有的n≥n0,有f (n)≤m a x{1, c} *g (n)。
例2-31 因爲limn→∞( 3n+ 2 ) /n = 3,所以3n+2=O (n);因爲limn→∞( 1 0n2+ 4n+ 2 ) /n2 = 1 0,所以1 0n2
+ 4n+2= O(n2 );因爲limn→∞( 6 * 2n + 2n ) / 2n = 6,所以6 * 2n + n2 =O (2n );因爲limn→∞( 2n2-3 ) /n4 = 0,所以2n2-3=O (n4 );因爲limn→∞( 3n2+ 5 ) /n=∞,所以3n2 + 5≠O (n)。
(2).W符號
W符號與大O 符號類似,它用來估算函數f 的下限值。
定義[符號]f(n)=(g (n)) 當且僅當存在正的常數c 和n0,使得對於所有的n≥n0,有f (n)≥cg(n)。
使用定義f (n) =(g (n)) 是爲了表明,函數f 至少是函數g 的c 倍,除非n 小於n0。因此對於足夠大的n (如n≥n0 ),g 是f 的一個下限(不考慮常數因子c)。與大O 定義的應用一樣,通常
僅使用單項形式的g 函數。
(3).小寫o符號
定義[小寫o] f(n)=o(g(n))當且僅當f(n)=O(g(n))且f(n)≠(g(n))。
例2-38 [小寫o] 因爲3n+ 2 = O (n2 )且3n+ 2≠(n2 ),所以3n+ 2 = o (n2 ),但3n+ 2≠o (n)。類似地,10n2 + 4n+ 2 = o(n3 ),但10n2 +4n+ 2≠o(n2 )。