1. 程式人生 > >資料結構和演算法總結(一)

資料結構和演算法總結(一)

任何一位有志於駕馭計算機的學生,都應該從這些方面入手,重點是:不斷學習,反覆練習,勤於總結。

究竟什麼是演算法呢?所謂演算法,是指基於特定的計算機模型,旨在解決某一問題而設計的一個指令序列。

演算法應具有以下流程:輸入與輸出;基本操作即加減乘除;確定性即明確的指令序列,可行性即可在對應計算機模型實現的指令序列;有窮性和正確性,有限步驟和正確結果。

演算法的特性:計算效率主要包括時間複雜度和空間複雜度。同一種演算法也有多種不同的實現方式從而效率不同。

在演算法的輸入,輸出還是中間結果,在計算機中都可以資料的形式表示,以及資料的儲存,組織,轉移和變換等操作,不同計算平臺和模型都會影響整體演算法的效率。即資料結構的重要性。我認為演算法更像一種思想,但依託於具體問題,具體資料結構的。所以以具體的資料結構來總結與學習演算法。最終的結果就是找到最優解或者憂解,也就是歸結於

問題結果的查詢問題。

時間複雜度:隨著輸入規模的擴大,演算法的執行時間將如何增長?

n\rightarrow T(n)的表示方法。但是對於小規模可忽略,但對於大規模可關注其漸進上界的大小可用\sigma 來表示表示,

T(n)= \sigma (f(n))(n)=\sigma (f(n)),有兩種意圖:有函式的常係數可以忽略為1,多項式中的低次項可忽略。

如何計算f(n)呢?其等於演算法所執行基本操作的總次數。基本操作為算數運算、比較、分支、子程式呼叫與返回等:而這些基本操作均可在常數時間內完成。在最壞情況下的響應速度才是唯一的指標。

當然還有對於應的最好時間複雜度\Omega,以及中間時間複雜度\Theta

空間複雜度:時間複雜度是其天然上界,現在主要是用加空間減時間。時間的重要性更強。

關於時間複雜度的取值情況:常數,對數(對於其底數的取值無所謂),對數多項式複雜度(雖不如常數複雜度但仍可接近)

,線性,多項式(實際應用中可接受),指數(實際應用無解,但小規模可能低於多項式)。

實際情況:絕大多數問題並不存在多項式時間演算法,有些問題還需要無窮時間。

輸入規模:用以描述輸入所需的空間規模。

資料結構:資料項之間的某種邏輯次序。包括線性結構,半線性結構,非線性結構。

線性結構:各資料項按照一個線性次序構成一個整體,主要包括向量,列表,棧與佇列。

半線性結構:樹結構,只要附加某種約束(遍歷),便可以在樹的元素之間確定某種線性次序。

非線性結構:相互之間均可能存在二元關係的一組物件,此類一般性二元關係,即為圖論。

基本資料結構有:向量,列表,棧與佇列,二叉樹,圖,搜尋樹。

高階資料結構:高階搜尋樹,詞典,優先順序佇列。

現代資料結構普遍遵從“資訊隱藏”的理念,通過統一的介面和內部封裝,構成ADT,抽象資料型別。

2.遞迴

遞迴是允許函式和過程進行自我呼叫。遞迴 的特點:簡潔,減少程式碼量,提高演算法的可讀性,保證演算法的整體效率。

遞迴形式:線性遞迴,二分遞迴,多分支遞迴,實現遍歷,分治等演算法策略。

1.線性遞迴

每一遞迴例項對自身的呼叫至多一次。於是每一層上至多隻有一個例項,且它們構成一個線性的次序關係。

應用問題可分解為兩個獨立的子問題,一個對應於單獨的某個元素,直接求解。另一個與原問題相同。往往對應與減而治之的策略:沒深入一層,問題規模均縮減為一個常數。例項:陣列求和。

遞迴跟蹤求遞迴的時間複雜度和空間複雜度:所有遞迴例項的建立、執行和銷燬所需的時間總和。正比於遞迴深度。

多遞迴基,多向遞迴。

尾遞迴:線上性遞迴演算法中,若遞迴呼叫在遞迴例項中恰好以最後一步操作的形式出現,則稱作尾遞迴。

尾遞迴的寫法只是具備了使當前函式在呼叫下一個函式前把當前佔有的棧銷燬,但是會不會真的這樣做,是要具體看編譯器是否最終這樣做,如果在語言層面上,沒有規定要優化這種尾呼叫,那編譯器就可以有自己的選擇來做不同的實現,在這種情況下,尾遞迴就不一定能解決一般遞迴的問題。例如:

一般遞迴:在函式 A 執行的時候,如果在第二步中,它又呼叫了另一個函式 B,B 又呼叫 C.... 棧就會不斷地增長不斷地裝入資料,當這個呼叫鏈很深的時候,棧很容易就滿 。
int func(int n)
{
    if (n <= 1) return 1;

    return (n * func(n-1));
}
理論上,在 func(n-1) 返回之前,func(n),不能結束返回。因此func(n)就必須保留它在棧上的資料,直到func(n-1)先返回。
int tail_func(int n, int res)
{
     if (n <= 1) return res;

     return tail_func(n - 1, n * res);
}
從上可以看到尾遞迴把返回結果放到了呼叫的引數裡。這個細小的變化導致,tail_func(n, res)不必像以前一樣,非要等到拿到了tail_func(n-1, n*res)的返回值,才能計算它自己的返回結果 -- 它完全就等於tail_func(n-1, n*res)的返回值。因此理論上:tail_func(n)在呼叫tail_func(n-1)前,完全就可以先銷燬自己放在棧上的東西。

實際上屬於尾遞迴的演算法均可以簡單的轉化為相應的迭代版本。

二分遞迴:面對輸入規模巨大時運用分而治之策略。每一次遞迴例項都可能做多次遞迴,故稱作“多路遞迴”。遞迴深度為O(logn)。必須保證子問題獨立。

若子問題不獨立呢?解決方法是:藉助一定量的輔助空間,在各子問題求解之後,及時記錄下其對應的解答。沒當遇到一個子問題,都首先查驗它是否已經計算過,以期通過直接查表獲得解答,從而避免重複計算。即所謂製表和記憶策略。也可以從遞迴基出發,自底而上遞推得出各子問題的解,即所謂的動態規劃演算法。