一.算法的定義
算法是解決特定問題求解步驟的描述,在計算機中表現為指令的有序序列,并且每條指令表示一個或多個操作
二.算法的定義
算法具有五個特性:輸入輸出,有窮性,確定性和可行性
-
輸入輸出
算法具有零個或多個輸入
算法至少有一個多個輸出,輸出的形式可以是打印輸出,也可以返回一個或多個值等 -
有窮性
有窮性:指算法在執行有限的步驟后,自動結束而不會出現無限循環,并且每一個步驟在可接受的時間內完成 -
可行性
可行性:算法的每一步都必須是可行的,也就是說,每一步都能通過執行有限次數完成
三.算法設計的要求
-
正確性
正確性:算法的正確性是指算法至少應該具有輸入,輸出和加工處理無歧義性,能正確反映問題的需求,能夠得到問題的正確答案
算法正確的四個層次:
1)算法程序沒有語法錯誤
2)算法程序對于合法的輸入數據能夠產生滿足需求的輸出結果
3)算法程序對于非法的輸入數據能夠得出滿足規格說明的結果
4)算法程序對于精心選擇的,甚至刁難的測試數據都有滿足要求的輸出結果
一般情況下,把層次3作為一個算法是否正確的標準 -
可讀性
可讀性:算法設計的另一目的是為了便于閱讀,理解和交流 -
健壯性
健壯性:當輸入數據不合法時,算法也能做出相關處理,而不是產生異常或莫名其妙的結果 -
時間效率高和存儲量低
時間效率是指算法的執行時間
數據算法應該滿足時間效率高和存儲量低的需求
算法效率的度量問題
-
事后統計方法
事后統計方法:這種方法主要通過設計好的測試程序和數據,利用計算機計時器對不同算法編制的程序的運行時間進行比較,從而確定算法效率的高低
有很大的缺陷:
1)必須依靠算法事先編好程序,這通常需要大量的時間和精力
2)時間的比較依賴計算機硬件和軟件等環境因素
3)算法的測試數據設計困難
所以這種方法一般不進行考慮 -
事前分析估算方法
事前分析估算方法:在計算機程序編制前,依據統計方法對算法進行估算
分析算法在計算機上運行所消耗的時間取決因素:
1.算法采用的策略,方法(算法好壞的根本)
2.邏輯產生的代碼質量(由軟件支持)
3.問題的輸入規模
4.機器執行指令的速度(硬件性能)
一個程序的運行時間,依賴于算法的好壞和問題的輸入規模.問題的輸入規模是指輸入量的多少
ok,讓我們活動下腦子,比較兩個求和的算法
//第一種算法
int i , sum = 0 , n = 100; //執行1次
for ( i = 1; i < n ; i++) //執行 n + 1次
{
sum = sum + 1; //執行n次
}
print ("%d",sum);//執行1次
//第二種算法
int sum = 0 , n = 100; //執行1次
sum = (1 + n) * n / 2; //執行1次
print("%d",sum);//執行1次
第一種算法執行了 1 + n + 1 + n + 1 = 2n + 3,
第二種算法執行了 1 + 1 + 1 = 3次.算法的第一條和最后一條語句是一樣的,所以我們只需要關心中間部分就可以了,我們把循環看成一個整體,忽略頭尾循環判斷的開銷,那么這兩個算法其實就是 n次與1次的差距.如果這個效果不明顯的話,在舉個例子
int i , j , x = 0,sum = 0, n = 100;//執行一次
for (i = 1; i <= n; i++) //執行 n + 1次
{
for (j = 1; j <= n; j++) //執行 n * n + 1次
{
x++;
sum = sum + x; //執行 n * n 次
}
}
print("%d",sum); //執行一次
這個算法的執行次數對于相同的輸入規模 n = 100,要多于前面的兩種算法,這個算法的執行時間隨著n的增加也將遠遠大于前面兩種算法
測試運行時間最可靠的方法就是計算對運行時間有消耗的基本操作的執行次數.運行時間與這個計數成正比
三,函數的漸進增長
-
一元一次方程的對比
x < 2 A的效率不如B
x = 2 兩種算法效率相同
x > 2 A的效率就由于算法B了,隨著n的增加,算法A比算法B越來越好
所有我們得出結論,算法A要比算法B要好
結論:輸入規模x在沒有限制的情況下,只要超過一個數值X,這個函數總是大于另一個函數,我們稱函數是漸進增長的
**函數的漸進增長:給定兩個函數f(n)和g(n),如果存在一個整數N,使得對于所有的n > N,f(n)總比g(n)大,那么我們就說f(n)的增長漸快于g(n),其中我們可以忽略不影響算法變化的加法常數.
一元一次方程,最高項的常數越大,算法的效率越低
-
一元二次與一元一次的對比
4x+8 = 2x^2 + 1
解得 x = sqrt(2.5) + 1 ,大約等于2.58
其中y是比較次數
n <= 3 ,C要差于D
n > 3, C要優于 D,在后來更是遠遠勝過D
- 去掉常數項
- 去掉與最高次項相乘的常數
去掉與x相乘的常數,或者去掉常數項,算法C的次數隨x的增長,都要遠遠小于算法D,也就是睡與最高次項相乘的常數并不重要
一元二次方程和一元一次方程進行對比,不管最高項的常數,二次效率要低于一次方程
- 一元二次方程和一元三次方程的對比
x = 1 兩種算法效率相同
x > 1,E的效率要優于F,隨著x的增長,差異越來越大
最高次項的指數大的,函數隨著n的增長,結果也變得增長的特別快
判斷一個算法效率時,函數中的常數和其他次要項長長可以忽略,應該關注主項(最高階項)的階數
當n的值變得非常大的時候,3n+ 1已經沒法跟2n^2的結果進行比較,算法G已經趨于算法I,所有我們可以得出結論,某個算法,隨著n的增大,它會越來越優于一種算法或越來越差于一種算法
四.算法時間復雜度
1.算法時間復雜度定義
在進行算法分析時,語句總的執行次數T(n)是關于問題規模n的函數,進而分析T(n)隨n的變化情況并確定T(n)的數量級.算法的時間復雜度,也就是算法的時間量度,記住:T(n) =O(f(n)).它表示隨問題規模n的增大,算法執行時間的增長率和f(n)的增長率相同,稱作算法的漸近時間復雜度,簡稱時間復雜度.其中f(n)是問題規模n的某個函數 .
用大寫的O()來體現時間復雜度的記法,我們稱之為大O記法.
一般情況下,隨著n的增大,T(n)增長最慢的算法為最優算法
由算法的時間復雜度定義可知,H,J,K算法的時間復雜度分別是O(1),O(n),O(n2).我們分別給它們取非官方的名稱,**O(1)叫常數階,O(n)叫線性階,O(n2)叫平方階**
2.推導大O階方法
f(n)相當于前面的求y的函數
-
1.用常數1取代運行時間內所有的加法運算
-
2.在修改后的運行次數函數中,只保留最高階項
-
3.如果最高階項存在且不是1,則去除與這個項相乘的常數
得到的結果就是大O階
事實上,分析一個算法的時間負責度,沒這么簡單,還需要分析幾個例子
3.常數階
int sum = 0, n = 100;//執行一次
sum = (1 + n) * n /2 ;//執行一次
print("%d",sum); //執行一次
為什么這個算法的時間復雜度不是O(3),而是O(1)那?????
算法的運行次數函數f(n) = 3.根據我們推導大O階的方法,第一步就是把常數項3改為1,在保留最高階項時發現,它沒有最高階項,所有算法的時間復雜度為O(1).
如果sum = (1+ n) * n /2 有10句
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;//執行10次
print("%d",sum);//執行1次
事實上,無論n為多少..上面兩段代碼就是3次和12次執行的差異,這種魚問題的大小無關(n的多少),執行時間恒定的算法.我們稱為具有O(1)的時間復雜度,又叫常數階
** 注意:不管這個常數是多少,我們都記做O(1),而不是O(2),O(3)等其他任何數字.**
對于任何分支結構而言,無論是真,還是假,執行的次數都是恒定的,不會隨著n的變大而發生變化,所有單純的分支結構(不包含在循環結構中),其時間復雜度也是O(1).
4.線性階
分析算法的復雜度,關鍵是要分析循環結構的運行情況
下面這段代碼,它的循環復雜度為O(n),因為在循環體中的代碼要執行n次
int i;
for (i = 0 ; i < n; i++){
時間復雜度為O(1)的程序步驟序列
}
5.對數階
int count = 1;
while(count < n){
count = count * 2;
}
有多少個2相乘后大于n,則會退出循環.由2^x = n 得到x = log2N,所有這個循環的時間復雜度為O(logn)
6.平方階
int i , j;
for (i = 0; i < n; i++){
for(j = i; j < n; j++) {
時間復雜度為O(1)的程序步驟序列
}
}
當i = 0時,內循環執行n次
當i = 1時,內循環執行n - 1次
當i = 2時,內循環執行n - 2次
......
當 i =