【一塊兒學習排序算法】1 算法特性及大O記法

本系列的文章列表和相關說明,請查看【一塊兒學習排序算法】0 序言
也能夠直接到github上查看完整的文章和源碼!javascript

排序算法

排序算法(Sorting algorithms)是什麼? Wikipedia 如是說:html

In computer science, a sorting algorithm is an algorithm that puts elements of a list in a certain order.java

也就是說,排序算法,就是某種算法,將列表中的元素按照某種規則排序。常見的如數字大小排序、字典序排序等。本系列例子約定爲從小到大的數字排序,其餘的相似,關鍵在於思路。git

算法特性

一、內部排序和外部排序github

按照數組規模的大小,排序能夠分爲內部排序外部排序
內部排序(internal sorting): 所有數組均可放在內存中排序。
外部排序(external sorting): 數組太大,不能所有放在內存中,部分數據在硬盤中。算法

本系列約定爲內部排序,關於海量數據的排序,後續補充。數組

二、穩定性
排序法的穩定性(stability): 取決於值相等的兩個元素,排序以後是否保持原來的順序。數據結構

三、比較排序和非比較排序
比較排序(comparison sort):
比較排序中,每一步經過比較元素大小來決定元素的位置。其複雜度由比較次數交換次數來決定。比較排序比較好實現,可是時間複雜度沒法突破O(nlogn)。證實過程,能夠參考這篇文章函數

非比較排序(non-comparison sort):
非比較排序,如桶排序,不經過比較,後續將會講解。這類算法能夠突破O(nlogn)post

排序算法有不少種,每一種都各自有本身的優勢缺點和不一樣的應用場景,沒有一種排序是絕對完美的。如何評價一個算法的優劣呢,咱們經過算法複雜度來衡量。

算法複雜度

算法複雜度(complexity),能夠從時間複雜度空間複雜度兩個維度來考慮。
空間複雜度,是指算法所須要的額外的存儲單元。目前的硬件條件,這一塊一般能夠不考慮了。算法優化,更可能是來優化算法的時間。

下面將介紹如何來估算時間複雜度。下面的介紹的方法,目前只夠勉強說服我本身。若是以爲不想了解這個理論,能夠直接記住下面的結論。若是以爲講得不是那麼容易懂,能夠參考別的資料仔細研究。

時間複雜度

若是一個列表的大小爲n,則算法耗費的時間T(n)。可是因爲機器、CPU等的不一樣,同一個算法執行的時間可能都不同。因此一般不是按耗費的時間來計算,而是用某個算法實現的指令執行的次數,來衡量時間複雜度。以下面這個程序:

for( i = 0; i < n; i++)   // i = 0; 執行1次
       			  // i < n; 執行n+1次
			  // i++ 執行n次
  sum = sum + i;          // 執行n次
  
// 總次數f(n) = 1 + n+1 + n +n = 3n+2
複製代碼

經過上面計數操做數的方法,顯得很麻煩。因此一般是經過一個函數來估算,確保它是算法操做數f(n)的上界。這種方法就是大O記法

大O記法

對於單調函數 f(n) 和 g(n), n爲正整數,若是存在常數c > 0, n0 > 0,且

f(n) ≤ c * g(n), n ≥ n_0

則咱們稱

f(n) = O(g(n))

以下圖所示。

大O記法

簡單來講,就是當n→∞時,f(n)的增加率不大於g(n),也就是說g(n)時f(n)的上界。 在這裏,f(n)就是算法的指令操做數,而g(n)就是咱們估算的複雜度上界。 它還有兩個特性。

O(g1(N)) + O(g2(N)) = max(O(g1(N)), O(g2(N)))
O(g1(N)) * O(g2(N)) = O(g1(N) * g2(N))

因此,上面程序的時間複雜度是:

f(n) = 3n+1 = O(1) + O(n) + O(n) + O(n) = O(n)
  • 常數時間 O(1)
    常數時間(constant time),算法的執行時間和列表大小無關。

  • 線性時間 O(n)
    線性時間(linear time), 算法執行時間和列表大小成正比。

  • 對數時間 O(logn)
    對數時間(logarithmic time), 稍微顯得難理解一點。不過若是你瞭解對數,其實也很簡單。例如二分查找,每一次查找都會去掉一半的元素,但最後一次元素個數就是1。假設數組大小爲n, 要通過x輪查找,則

    n * (1/2)^x = 1
    x = log_{2}n

logn是簡寫,通常忽略底數。

  • 二次項時間 O(n2)
    二次項時間(quadratic time), 一般是兩層循環的算法。

簡易估算方法

對於一個算法的時間複雜度,根據以上理論,大致按下面的步驟來估算複雜度。 以這個程序爲例:

sum = 0;            
for( i = 0; i < n; i++)
    for( j = i; j < n; j++)
        sum++;
複製代碼

1. 忽略簡單語句
對於簡單複雜語句,它執行次數是一個常數,複雜度爲O(1)。若是還存在循環,O(1)對結果不影響。

2. 關注循環語句
對於循環語句,要認真分析其循環執行的次數。例子中,外層循環要執行 n 次,內層循環要

n + (n-1) + ... + 2 + 1 = (n + 1)/2

因此總次數T(n)爲

T(n) = n * (n+1)/2 = 1/2*n^2 + 1/2*n

3. 忽略常數項,保留高次項
對於一個多項式,當n→∞時,徹底由最高項次決定。因此

T(n) = O(1/2*n^2 + 1/2*n) = O(n^2 + n) = O(n^2)

對於有的程序,複雜度仍是很很差計算。因此要多練習,寫一個程序以後,本身主動去算一下它的複雜度,慢慢就熟練了。

算法評價

對於排序算法,一個算法的執行性能,和輸入的數據有很大的關係。對於某些特定的數據,某些算法的效率很高,但一般算法的性能又很低。因此一般存在:

  • 最優時間複雜度:某些數據,執行的次數最少
  • 最差時間複雜度:某些數據,執行的次數最多
  • 平均時間複雜度:平均須要執行的次數

一般仍是以平均時間複雜度,來衡量算法。例如冒泡排序,當數組元素有序時,最優時間複雜度爲O(n)。當逆序是,爲O(n2)。平均仍是O(n2)。算法複雜度的優劣,能夠參考此圖:

算法比較

總結

本章節主要介紹了一下排序算法的類型,以及若是經過大O記法來評價一個算法。對於如何計算算法的時間複雜度,不少人都感受很頭疼。我給的建議是,按照上面的步驟多練習,多去主動算程序的時間複雜度。這樣慢慢本身就會掌握技巧,而且提醒本身保證本身程序的執行效率。共勉!

資源與參考

[1] About the #sorting-algorithms series
[2] 凱耐基梅隆大學數據結構與算法-排序算法
[3] CMU algorithm complexity
[4] Big O cheat sheet
[5] You need to understand Big O notation, now

相關文章
相關標籤/搜索