寫在前面
前面學習完了Python基礎內容後,從本節開始正式學習資料結構與演算法相關內容。這是一個比較複雜的主題,一般分為初級、進階、以及專門的演算法分析三個階段來學習,因此我們也需要循序漸進。本節主要熟悉資料結構與演算法中一般概念,然後熟悉演算法效率分析的大O記法,知識結構如下圖所示:
什麼是演算法。 1)演算法的定義
演算法(Algorithm),指的是對特定問題求解步驟的一種描述。
在數學上,它是運算步驟的有限序列,每一步代表執行某種運算。例如,手動計算兩個整數和的演算法描述為: 將兩個數字對齊寫在紙上,然後從個位開始,逐位求和,遇到和大於10則向高位進位,最終計算出兩個數字之和。
在電腦中,演算法是指令的有限序列,每條指定代表一個或者多個操作。例如,在12306網站完成車票查詢、車票訂購等任務,在電腦上都是由一系列演算法實現。
在利用電腦求解問題的過程中,我們首先對問題進行建模,構造合適的演算法,然後編寫相關程式,流程如下(來自Introduction to Algorithm):
2)演算法的5大特性
對於一個演算法,有5大特性,列出如下:
輸入 一個演算法有零個或者多個外部輸入,注意可以沒有輸入。
輸出 一個演算法有一個或者多個輸出,注意演算法必定有某種形式的輸出。
有窮性(Finiteness) 演算法必須在有限步驟內完成。
確定性(Definiteness ) 演算法中每條指定必須沒有二義性(只有一種解釋),在任何條件下,演算法只有唯一的一條執行路徑,對於相同的輸入只能得到相同的輸出。
可行性(Effectiveness ) 演算法中描述的操作都可以通過已經實現的基本操作執行有效次實現,具有可行性。 3) 演算法評價的因素
解決同一個問題有不同的方法,這些方法之間如何比較和選擇成為一個關鍵。
例如去不同城市,可以選擇的交通方式有火車、輪船、飛機、自駕、客運大巴、拼車等多種,這些不同的旅行方式,在舒適度、價錢、時間、安全等方面各有不同,需要從多個角度比較和選擇。
評價演算法好壞也有各種因素,主要包括下面幾個因素:
正確性 是否正確地解決了問題。
可讀性 演算法主要是人來編寫,其次才是機器執行。是否容易理解成為實現和維護的關鍵。
實現難度 演算法是否容易實現。
儲存開銷 演算法消耗的記憶體、外儲存空間合理嗎。
執行時間 演算法執行時耗時能接受嗎。
健壯性 程式遇到非預期輸入能否做出合理反應? 例如簡單的計算程式,遇到除0操作時,應該提醒使用者錯誤,而不是程式崩潰掉。 什麼是資料結構。 1)抽象資料類型
利用電腦求解問題的首要步驟是對問題進行建模,在建模的過程中,我們需要考慮到資料的輸入、處理、輸入等內容,演算法描述了操作這些資料的具體流程,但是如何表示和儲存問題模型中的資料則需要選擇或者重新設計一種有利的結構。
抽象資料類型(Abstract Data Types,ADT),是一種理論上的概念,它從邏輯層面,描述了可能值範圍、允許的操作以及操作的行為表現。ADT與具體的實現細節無關(implementation-independent )。例如整數,是一種ADT,它可能的值包括-1,0,1…,允許的操作包括加減乘除,以及大於、小於比較等。這些是數學上的模型,與在計算中具體如何表示無關。 2)資料結構
ADT是一種理論上的數學模型,而資料結構則是電腦上對這個抽象資料類型的實現,是實現層面的概念,由具體電腦語言以及這個語言的基礎類型來實現。 3)ADT與資料結構的區別
從上面的定義可以看出了它們之間的差別。例如棧(Stack)是一種ADT,定義了它是先進後出的結構,支援的操作包括:入棧(push)、出棧(pop)、查看棧頂元素(top)、判斷棧是否為空白(empty)等4種操作。在計算上可以通過數組實現,稱為ArrayStack,或者通過鏈表實現,稱為LinkListStack。這兩種具體實現稱之為棧的資料結構。 演算法效率評判標準
上面提到了,如果我們評價交通工具,我們可能會選擇舒適度、時間、安全、價格等標準進行評判,與此類似,評判一個演算法好壞也需要一些標準。
對一個演算法進行空間和時間複雜度分析時,可以通過執行完程式後進行統計分析(事後統計方法),也可以在未執行程式時就進行理論分析(事前估算分析估計方法)。對於同一個演算法,在不同的機器上執行,受到處理器、機器字長、儲存空間、指令集等的影響,已耗用時間存在差異,例如運行在“天河一號”超級電腦和普通PC上的程式,已耗用時間就可能大不相同;同一個演算法,即使利用同一台機器來運行程式,但採用C或者Ada編寫的程式就比用Basic或者Lisp編寫的快約20倍。因此,事後統計分析的方法很多時候並不可靠(為特定裝置編寫的程式進行效能比較除外),因此人們常常採用事先分析估算的方法。
既然事先估算方法,並沒有實際執行程式,使用絕對單位的位元組大小或者時間長度,顯然是不可能的了,應該使用某種理論上的抽象標準。在上面我們提到了諸如可讀性、正確性等因素,這些因素是每個好的演算法都必須具備的,這些因素沒有區分度,真正具有區分度的因素是空間複雜度(Space Complexity)和時間複雜度(Time Complexity)兩個標準。這兩個複雜度,一般隨著問題輸入的資料量,即與問題規模n,成某種函數關係,例如時間複雜度可以表示為:
T(n)=f(n) T(n)=f(n)。
在尋求這個函數關係時,我們首先找出一種被作為基本操作(Elementary operation)的運算,估算它的執行次數與n的關係。所謂基本操作指的是演算法中對時間有著關鍵影響,與問題規模成正比的操作。例如檢查一個元素x是否在一組數字a中,比較x與a中某個元素值是否相等的操作,就可以視為基本操作。
def find_in_array(array,val): """ naive search algorithm :param array: input elements array :param val: the value to search :return: index if found or -1 """ for i, x in enumerate(array): if x == val: # 基本操作 return i return -1
在上面的尋找過程中,我們會遇到3種情形: 最壞情況下(worst-case) 要尋找的元素在數組最後一個位置 T(n)=n T(n)=n 平均情況下(average case) 假定每個元素被尋找的機率相同,則平均尋找時需要的比較次數為: T(n)=∑ni=11n∗i=1+n2 T(n)=\sum_{i=1}^{n}\frac{1}{n}*i=\frac{1+n}{2} 最好情況下(best-case) 要尋找的元素在數組第一個位置 T(n)=1 T(n)=1
下面我們來重點熟悉時間複雜度的大O記法。 漸進分析法 1)什麼是漸進分析法?
漸進分析法(Asymptotic Analysis)的目標是尋找到問題處理的時間與問題規模之間,隨著問題規模變大時的一種上限和下限關係,通過上限我們瞭解到演算法最壞情況,通過下限瞭解到演算法最好的情況。
大O定義: 假設 f(n) f(n)是演算法時間複雜度的表示,而 g(n) g(n)是其中最具影響的因子,如果: f(n)<=Cg(n) f(n) ,對於所有的 n>=n0,C>0,n0>=1 n >= n_{0}, C > 0,n_{0} >= 1都成立,則我們可以將 f(n) f(n)記為: f(n)=O(g(n)) f(n)=O(g(n))
上面定義中,最具影響的因子,是複雜度運算式中,對結果影響最大的部分,例如: f(n)=5n2+2n+1 f(n)=5n^2 + 2n + 1,那麼當n增大時,顯然 n2 n^2 決定了 f