什么是算法
算法字面意思,計算方法;
算法規定了求解給定類型問題所需的所有處理步驟以及執行順序,使得問題能在有限時間內機械的求解,一個算法就是對特定問題求解步驟的一種描述,再具體一點,算法是一段有窮的指令序列;算法必須能使用某種語言描述;
例如:
計算1到5的和 ,這個需求,如何來實現,第一步做什么,第二步做什么,整個計算步驟和執行順序統稱為算法,如果最終能夠在有限的步驟下求出正確的和,那這就是一個合格的算法;
算法的特點:
-
有窮性
算法必須在執行有窮步后結束
-
確定性
算法的每一個步驟都必須是明確定義的,
-
可行性
算法中的每一步都是可以通過已實現的操作來完成的
-
輸入
一個算法具備0或多個輸入
-
輸出
一個算法有一個或多個輸出,它們與輸入有着特定的關系
算法與程序的區別,算法只是一種描述,可以使用任何語言,但是通常不能直接被計算機運行,而程序則是算法的具體實現,使用某種計算機語言;
算法設計應滿足的要求
-
正確性:對於合法的輸入產生符合要求的輸出
-
易讀性:算法應該盡可能易讀,便於交流,這也是保證正確性的前提(注釋可提高易讀性)
-
健壯性:當輸入非法數據時,算法可作出適當反應而不至於崩潰(例如輸出錯誤原因);
-
時空性:指的是算法的時間復雜度和空間復雜度,算法分析主要也是分析算法的時間復雜度和空間復雜的,其目的是提高算法的效率;
算法分析
解決同一問題的算法可能有多種,我們希望從中選出最優的算法,效率高的,占用空間小的,為此我們就需要對算法進行評估和分析;
通常評估算法根據兩個度量
- 時間復雜度:算法運行完成所需的總步數(標准操作),通常是問題規模的函數
- 空間復雜度:算法執行時所占用的存儲空間,通常是問題規模的函數
時間復雜度
確定算法的計算量
-
合理選擇一種或幾種操作作為'標准操作',無特殊說明默認以賦值操作作為標准操作;
-
確定算法共執行多少次標准操作,並將此次數規定為算法的計算量
-
以算法在所有時輸入下的計算量最大值作為算法的最壞情況時間復雜度
-
以算法在所有時輸入下的計算量最小值作為算法的最好情況時間復雜度
-
以算法在所有時輸入下的計算量平均值作為算法的平均情況時間復雜度
-
最壞和平均情況時間復雜度統稱為時間復雜度;
-
它們通常擁有相同的大O表示法
-
如:最壞:O(n) 平均O(n/2) 忽略系數后都為O(N)
-
注意:時間復雜度通常以量級來衡量,也就是說不需要精確的計算到底執行了幾步,而是得出其計算量的數量級即可,並忽略常數,因為當數量級足夠大時,常數對於計算量的影響可以忽略不計;
如: (n-1)(n-2) 數量級為 n^2
時間復雜度使用大O表示,如O(1)
案例:
1.
void aFunction(){
int c = 10 + 20;
int d = c * c;
printf(d);
}
上列算法若以賦值運算作為標准操作,則該算法的計算量為2,其時間復雜度記為O(1),為什么是O(1)呢,是因為2是一個常數,常數對於函數的增長影響並不大,所以計算量為常數時表示為O(1),按照這種方式,即使計算量為2000,同樣記為O(1),稱為常數階
2.
void bFunction(int n){
for(int i = 0;i < n;i++){ // n
int c = 2 * i;// 1
int d = 3 * i;// 2
}
}
此時函數的循環次數由未知數n來決定,循環體內計算量為2,當n是一個自然數時,函數的計算量等於(n)(2),此時時間復雜度為O(n),無論用常數2對n進行加減乘除對於n的指數都沒有影響,當n足夠大時,內部的2次計算量可以忽略,所以記為O(n),稱為線性階
更粗陋的度量方法是函數體包含一層循環時記為O(n)
3.
void bFunction(int n){
for(int i = 0;i < n;i++){
for(int j = 0;j < i;j++){
}
}
}
外層循環次數為n,內層循環次數隨着n的增長而增長且最大值為n-1次,那么整個函數的計算量為(n)(n-1),常數可以忽略,所以時間復雜度記為O(n^2) ,稱為平方階
粗陋的方法是有兩層嵌套循環,且循環次數都隨着n的增長而增長,所以是O(n^2),以此類推,但是要注意下面這種情況
4.
void bFunction(int n){
for(int i = 0;i < n;i++){
for(int j = 0;j < 3;j++){
}
}
}
此時內層循環的循環次數是固定(常數)所以不會影響計算量的數量級,時間復雜度記為O(n)
5.
void bFunction(int n){
for(int i = 3;i < n;){
i *= 3;
}
}
此時函循環次數會隨着3循環體中的常數3的的變化而變化,我們可以用對數來表示,
假設循環次數為s,循環條件可表示為 s = 3^s < n;(即i本身為3,其次冪會不斷的增長,但結果要小於n)
當然這里有個條件是i的初值必須和每次乘等的值相同,形成次冪的增長;
用對數表示為s = log3n,時間復雜度記為O(log3n),常數可以忽略,所以最后是O(logn)稱之為對數階
對數階的數量級小於線性階,因為若n的值相同,對數階計算量必然小於線性階
6.
void bFunction(int n){
for(int i = 0;i < n;i++){
for(int j = 0;j < n;j++){
for(int k = 0;k < n;k++){
}
}
}
}
上述算法時間復雜度為O(n3),3為常數,對應着循環的嵌套層數即次冪為常數;也可以用O(nC)表示,稱為多項式階
7.
void bFunction(int n){
int num = n;
for(int i = 0;i < n;i++){ //O(n)
num *= n;
}
for (int j = 0;j<num;j++){ //O(n)
}
}
上述函數輸入的參數n將作為num的次冪,假設循環次數為s, s = 2n,那么時間復雜度為O(2n),
稱之為指數階,可記為O(C^n)
8.
void bFunction(int n)
{
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
}
}
for(int i=0;i<n;i++){
}
}
對於順序運行的算法,總時間復雜度等於算法中最大時間復雜度,即O(n^2)
9.
void bFunction(int n)
{
if(n % 2 ==0){
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
}
}
}else{
for(int i=0;i<n;i++){
}
}
}
對於具備分支結構的算法,總時間復雜度等於算法中時間復雜度最大路徑的復雜度,即O(n^2)
時間復雜度大小順序
常數 < 對數階 < 線性階 < 平方階 < 多項式階 < 指數階
O(1) < O(logn) < O(n) < O(n^2) < O(n^C) < O(C^n)
一般情況下,一個算法的時間復雜度是算法輸入規模的函數;
通常認為,具有指數階數量級的算法是實際不可計算的,而量級低於平方階的算法是高效的;
空間復雜度
確定算法的空間占用量
空間復雜度是對一個算法在執行過程中臨時占用存儲空間大小的度量;
一個算法在執行期間所需要的存儲空間包括以下部分:
- 程序代碼本身所占用的空間
- 輸出數據所占用的空間
- 輔助變量所占用的空間
** 估算算法空間復雜度時,一般值分析輔助變量所占用的空間;
程序代碼占用空間是固定的,通常比較小
輸入數據占用空間同樣較小
強調:無論是時間復雜度還是空間復雜度都用大O表示,表示方法也是相同的,不需要具體到幾次,只需要求出最大的數量級即可,同樣忽略系數,即復雜度對於常數的加減乘除是忽略不計的;
案例:
下列是兩個算法,用於對數組元素順序進行逆轉操作;
void f1(int a[],int n){
int i,temp;
for(i = 0;i < n/2-1;i++){
temp = a[i];
a[i] = a[n-1-i];
a[n-1-i] = temp;
}
}
上述算法時間復雜度為O(n);空間復雜度為O(1);
解析:該算法員工定義了兩個整型的輔助變量,i和temp,輔助變量的個數與輸入數據沒有關系,是固定的常量C,對於擁有常數階復雜度的算法,其復雜度用O(1)表示;
void f2(int a[],int n){
int i,b[n];
for(i = 0;i < n;i++){
b[i] = a[n-i-1];
}
for(i = 0;i < n;i++){
a[i] = b[i];
}
}
上述算法時間復雜度為O(n);空間復雜度為O(1);
解析:
對於空間復雜度,其需要的計算量是(n)(2),大O表示為O(n);
對於空間復雜度,同樣是兩個變量,但是數組b的空間大小是隨着輸入數據增長的,所以整體占用為 1 + n,大O表示為O(n);
可以發現空間復雜度相比時間復雜度更好度量,因為只需要根據定義的變量數量來計算即可,而時間復雜度會隨循環語句而變化;
