在參加面試的時候,多多少少都會問到一些關於演算法的知識。
這其實是有原因的:在多個人專業知識相同的情況下,公司為什麼選擇放棄他人而選擇你,其中的乙個因素就是看你的演算法基礎。
根據《演算法導論第三版》中的描述:演算法就是任何問題的解決過程,它接收一些值或集合,對這些值或集合進行加工,最後產生一些值或集合作為輸出,演算法指的就是將輸入轉換為輸出這個過程中的一系列計算流程。
簡而言之,我們可以說演算法就是解決乙個特定任務的一系列步驟。
乙個演算法應具有以下五個重要特徵:
- 有窮性:演算法必須在執行有限個步驟後終止,如果你設計的演算法永無休止地嘗試解決問題,那麼它是無用的;
- 確切性:演算法的每一步指令都必須具有確切的意義,並且在任何場景下,每一步指令都應該都沒歧義;
- 有效性(可行性):乙個演算法設計出來是用以解決某個問題的,如果你設計的演算法不能解決問題,那麼它也是無用的;
- 輸入項:乙個演算法有0個或者多個輸入,用來初始化演算法,這主要看你要解決的問題需不需要輸入引數;
- 輸出項:乙個演算法有1個或者多個輸出。沒有輸出項的演算法是毫無意義的,輸出項反映了資料加工後的結果。
針對同乙個問題,可以有多種解決方案。
具體用哪種就要由每個演算法的效率來決定了,乙個演算法的優劣將會大大影響程式的執行效率。
那麼如何分析乙個演算法的優劣呢?主要是從時間複雜度和空間複雜度來考慮:
1. 時間複雜度
時間複雜度是指執行演算法所需要的計算工作量,簡單點說就是指執行演算法所需要消耗的時間。
乙個演算法執行所耗費的時間,從理論上來講是不能算出來的,必須通過計算機執行測試才能知道。
不過乙個演算法具體耗費多少時間我們並不關心,我們只需要知道哪個演算法花費的時間多,哪個演算法花費的時間少就可以了。
這裡我們做乙個假設:一條語句執行消耗的時間量為s,a演算法中有n條語句,那麼執行a演算法所需要的時間就是n*s;b演算法中有n-2條語句,那麼b演算法所消耗的時間就是(n-2)*s。在其他因素全都相同的情況下,我們就可以得出,b演算法是優於a演算法的,因為執行b演算法所消耗的時間更多。
簡單總結,乙個演算法花費的時間與演算法中語句的執行次數成正比例。哪個演算法中語句執行次數多,它花費的時間就多。
此時我們引入時間頻度的概念,記為t(n)。
n指的是問題的規模,當n不斷變大時,時間頻度t(n)也會不斷變大。
我們想要呈現出它的變化規律,為此,我們引入了時間複雜度的概念。
一般來說,假設這個演算法是問題規模為n的函式f(n),那麼時間複雜度就記做:
t(n) = o(f(n))
o在表示式中代表著最壞的情況,這裡最壞的情況就是n = 無窮大。
簡單來說,上述公式表達的含義就是在n趨於無窮大的時候,t(n) = f(n)。
f(n)指的是具體的函式,雖然對f(n)沒有規定,但是我們一般盡可能地去簡化f(n)。
例如:o(n2+n+1)=o(3n2-n+5)=o(9n2+5)=o(n2)
注意o中是攜帶乙個常數c的,所以f(n)一般不加係數。
為什麼可以這樣換算,為什麼最後都可以換算為o(n2)?
這主要是因為當n趨於無限大時, n2 是遠遠大於 cn的,也就是高次方項遠遠大於低次方項,導致低次方項完全可以忽略不計。
這就像是什麼,我們把n2+3n+1想象為一棵樹的話,n2就是主幹部分,3n+1就是一些細枝末節,是完全可以忽略的。
下面我們就來舉幾個示例,計算它們的空間複雜度:
第一題:
int x = 0; //執行1次
int y = 5; //執行1次
x = 10; //執行1次
intsum = x+y; //執行1次
以上四條語句的頻度均為1,該程式的執行時間是乙個與n完全無關的常數。演算法的時間複雜度就是常數階,記做:t(n) = o(1)
假設這個演算法有上千條語句,因為它的執行時間不會隨著n的增長而變化,其執行時間也不過是乙個較大的常數。
此類與n毫無關聯的演算法的空間複雜度均可以記為:t(n) = o(1)。
第二題:
int number = 0; //執行一次
for(int i =0;i//執行n+1次,注意為什麼是n+1,因為當條件不滿足時,也執行了一次判斷
number++; //執行n次
}
語句一的頻度為1。
語句二的頻度為n+1。
語句三的頻度為n。
所以上述**的時間複雜度為:t(n) =1 + n+1 + n = 2n+2。
接著當n趨於無限大時,我們只需保留最高的次方項,即:t(n) = o(2n),接著我們將係數省略到o中,即t(n)=o(n)。
第三題:
int number = 0; //執行1次
for(int i =0;i//執行n+1次
for(int j =0;j//執行(n+1)*n次
number++; // 執行n*n次}}
第三題可能有些難度,是乙個巢狀的for迴圈,我們來一句一句分析。
語句一是乙個頻度為1的語句,和n無關,記為1。
語句二是乙個外層for迴圈,執行次數為n+1,1代表的是當條件不滿足時,結束迴圈的那次操作。
語句三是巢狀的for迴圈,自身的執行次數也是n+1,但是配合上外層的for迴圈,將會執行n次巢狀for迴圈,所以語句三的頻度為:(n+1)*n。
語句四是巢狀for迴圈中的語句,一次巢狀for迴圈中的語句需要執行n次,而巢狀for迴圈也需要執行n次,所以語句四的頻度為:n*n。
那麼整個程式的時間複雜度即為:t(n) = 1+ n+1 + (n+1) * n + n * n = 2n2 +2n + 2。
當n趨於無限大時,只保留最高次方項,並且省略係數,即:t(n) = o(n2);
通過上面三題的練習,各位看官對時間複雜度的計算過程應該已經了解了。
其實時間複雜度的真正計算,還是比較簡單的,難在時間複雜度概念理解。
接下來我們來看一下常見的時間複雜度函式有哪些:
從圖中可以看出,隨著n的變化,時間複雜度各個函式的變化規律。
這些常見的時間複雜度大小排序如下:ο(1)<ο(log2n)<ο(n)<ο(nlog2n)<ο(n2)<ο(n3)<…<ο(2n)<ο(n!)
我們應該盡可能使用時間複雜度小的演算法,從而提公升程式的執行效率。
2. 空間複雜度
演算法的空間複雜度指的是乙個演算法所消耗的儲存空間。
乙個演算法在計算機所占用的空間,由三方面決定:
- 儲存演算法本身所占用的空間;
- 演算法輸入輸出的資料所占用的空間;
- 演算法在執行過程中臨時占用的空間。
儲存演算法本身所占用的空間與演算法書寫的長短成正比,要壓縮這方面的儲存空間,就必須編寫出較短的演算法。
演算法輸入輸出資料所占用的空間是由這個演算法要解決的問題決定的,針對同一問題,它占用的空間不會隨著演算法的變化而改變,但它確實占用著空間。
演算法在執行過程中臨時占用的空間會根據演算法的不同而產生差異,也是我們最需要著重關心的一點。
有些演算法只需要占用很少的臨時工作單元,並且不會隨問題規模的變大而變大,我們稱這種演算法是「就地」進行的,是節省儲存的演算法。
有些演算法占用的臨時工作單元數與解決的問題規模n有關,占用空間隨著n的增大而增大,當n較大時,將占用較多的儲存單元,一些常見的排序演算法就屬於這種情況,比如快速排序、歸併排序,屬於占用較多儲存空間的演算法。
當乙個演算法的空間複雜度不會隨著問題規模的變化而變化時,可表示為o(1)。
當乙個演算法的空間複雜度與問題規模n呈線性比例關係時,可表示為o(n)。
同理,其他變化關係也可以推導出來。同時間複雜度相比,空間複雜度的分析還是比較簡單的。
3. 其他
除了上述兩條評定規則外,評價乙個演算法的質量還有其他幾個因素:
- 正確性:演算法的正確性是評價乙個演算法優劣最重要的標準!這個演算法的結果是錯誤的,那麼這個演算法毫無意義。
- 可讀性:可讀性指的是人們理解這個演算法的難易程度,當時間複雜度與空間複雜度相同的情況下,當然要選擇易於人們理解的演算法。
- 健壯性:健壯性是指乙個演算法對不合理資料輸入的反應能力和處理能力,也稱為容錯性。
本文是演算法最基本的一些知識,看完本文,需要你完全理解下圖的含義:
後續還會針對演算法,做一些更深入的分析,感謝各位看官的瀏覽。
鞠躬。
演算法的時間複雜度和空間複雜度-總結
演算法導論學習筆記之演算法基礎篇
一 插入排序 插入排序屬於原址排序,演算法在陣列a中重排元素,演算法思想與玩撲克牌時依次將抓到的牌放到手中合適的位置一致,當輸入完成時,手中的牌即已完成排序。插入排序 a for j 2.a.length setp 1 a 下標從 1 開始計數 key a j i j 1 while i 0 a i...
演算法導論學習總結 基礎篇 一
一 基礎知識 概念 總結 1.漸進記號 1 大o記號 大o記號給出函式的漸進上界。定義 o g n ps 記號是乙個比o記號更強的概念。按集合論的寫法,有 g n 包含於 o g n 2 大 記號 正如o記號提供了乙個函式的漸進上界,記號提供了漸進下界。定義 g n 3 大 記號 大 記號給出函式的...
演算法學習 動態規劃基礎篇
動態規劃 英語 dynamic programming,簡稱dp 是一種在數學 管理科學 電腦科學 經濟學和生物資訊學中使用的,通過把原問題分解為相對簡單的子問題的方式求解複雜問題的方法。動態規劃常常適用於有重疊子問題 1 和最優子結構性質的問題,動態規劃方法所耗時間往往遠少於樸素解法。動態規劃背後...