你需要知道的演算法之基礎篇

noonnnne發表於2017-12-18

0 - 前言

很多時候我們都會感慨:要是當時×××了多好啊,現在也不至於這樣難堪了。

後悔的時候千千萬,一覺醒來過眼雲煙。

我也和芸芸眾生一樣在學校的時候沒有好好的理解思考一些東西,等到了真正需要用的時候才知道書到用時方恨少。有道是知錯能改,那為什麼有道 == 知錯能改呢?下面請允許我開始真正的內容:

本文通篇都是一些概念,但是你需要知道這些更有利於理解時間複雜度等一些概念是什麼、怎麼來的、為什麼需要這個東西(what、where、why)。

1 - 演算法

演算法的定義是這樣的:解題方案的準確而完善的描述,是一系列解決問題的清晰指令。巴拉巴拉的,雖然是一小句但還是不想看(題外話:有時候吧專業名詞記下來面試的時候還是挺有用的),其實就是解決一個問題的完整性描述。只不過這個描述就可能是用不同的方式或者說是“語言”了。

2 - 演算法的效率

既然演算法是解決問題的描述,那麼就像一千個人眼中有一千個阿姆雷特他大姨夫一樣,解決同一個問題的辦法也是多種多樣的,只是在這過程中我們所使用/消耗的時間或者時間以外的代價(計算機消耗的則為記憶體了)不一樣。為了更快、更好、更強的發揚奧利奧..哦不,提高演算法的效率。所以很多時候一個優秀的演算法就在於它與其他實現同一個問題的演算法相比,在時間或空間(記憶體)或者時間和空間(記憶體)上都得到明顯的降低。

所以呢,演算法的效率主要由以下兩個複雜度來評估:

  1. 時間複雜度:評估執行程式所需的時間。可以估算出程式對處理器的使用程度。
  2. 空間複雜度:評估執行程式所需的儲存空間。可以估算出程式對計算機記憶體的使用程度。

設計演算法時,時間複雜度要比空間複雜度更容易出問題,所以一般情況一下我們只對時間複雜度進行研究。一般面試或者工作的時候沒有特別說明的話,複雜度就是指時間複雜度。

2.0 - 時間複雜度

接下來我們還需要知道另一個概念:時間頻度。這個時候你可能會說:“不是說好一起學演算法嗎,這些東東是什麼?贈品嗎?”。非也非也,這是非賣品。

因為一個演算法執行所消耗的時間理論上是不能算出來的,沒錯正是理論上,so我們任然可以在程式中測試獲得。但是我們不可能又沒必要對每個演算法進行測試,只需要知道大概的哪個演算法執行所花費的時間多,哪個花費的時間少就行了。如果一個演算法所花費的時間與演算法中程式碼語句執行次數成正比,那麼那個演算法執行語句越多,它的花費時間也就越多。我們把一個演算法中的語句執行次數稱為時間頻度。通常(ps:很想知道通常是誰)用T(n)表示。

在時間頻度T(n)中,n又代表著問題的規模,當n不斷變化時,T(n)也會不斷地隨之變化。為了瞭解這個變化的規律,時間複雜度這一概念就被引入了。一般情況下演算法基礎本操作的重複執行次數為問題規模n的某個函式,用也就是時間頻度T(n)。如果有某個輔助函式f(n),當趨於無窮大的時候,T(n)/f(n)的極限值是不為零的某個常數,那麼f(n)T(n)的同數量級函式,記作T(n)=O(f(n)),被稱為演算法的漸進時間複雜度,又簡稱為時間複雜度

2.1 - 大O表示法

用O(n)來體現演算法時間複雜度的記法被稱作大O表示法

一般我們我們評估一個演算法都是直接評估它的最壞的複雜度。

大O表示法O(f(n))中的f(n)的值可以為1、n、logn、n^2 等,所以我們將O(1)、O(n)、O(logn)、O( n^2 )分別稱為常數階、線性階、對數階和平方階。下面我們來看看推導大O階的方法:

推導大O階

推導大O階有一下三種規則:

  1. 用常數1取代執行時間中的所有加法常數
  2. 只保留最高階項
  3. 去除最高階的常數

舉好多栗子

  • 常數階
let sum = 0, n = 10; // 語句執行一次
let sum = (1+n)*n/2; // 語句執行一次
console.log(`The sum is : ${sum}`) //語句執行一次
複製程式碼

這樣的一段程式碼它的執行次數為 3 ,然後我們套用規則1,則這個演算法的時間複雜度為O(1),也就是常數階。

  • 線性階
let i =0; // 語句執行一次
while (i < n) { // 語句執行n次
  console.log(`Current i is ${i}`); //語句執行n次
  i++; // 語句執行n次
}
複製程式碼

這個演算法中程式碼總共執行了 3n + 1次,根據規則 2->3,因此該演算法的時間複雜度是O(n)。

  • 對數階
let number = 1; // 語句執行一次
while (number < n) { // 語句執行logn次
  number *= 2; // 語句執行logn次
}
複製程式碼

上面的演算法中,number每次都放大兩倍,我們假設這個迴圈體執行了m次,那麼2^m = nm = logn,所以整段程式碼執行次數為1 + 2*logn,則f(n) = logn,時間複雜度為O(logn)。

  • 平方階
for (let i = 0; i < n; i++) { // 語句執行n次
  for (let j = 0; j < n; j++) { // 語句執行n^2次
    console.log('I am here!'); // 語句執行n^2
  }
}
複製程式碼

上面的巢狀迴圈中,程式碼共執行 2*n^2 + n,則f(n) = n^2。所以該演算法的時間複雜度為O(n^2 )

常見時間複雜度的比較

常見的時間複雜度函式相信大家在大學中都已經見過了,這裡也不多做解釋了:

O(1)<O(logn)<O(n)<O(nlogn)<O(n²)<O(n³)<O(2ⁿ)<O(n!)

3 - 結語

曾經有一份能夠在學校好好學演算法的機會,我沒有好好珍惜,直到失去的時候我才追悔莫及。

從今天開始,我會好好的把之前很多自己沒有去理解的各種基礎、底層知識好好的嚼幾遍。與諸君共勉!

原文地址: 點選此處

相關文章