本系列前面兩篇講的都是一些背景知識,從這一篇開始我們正式講演算法,從演算法的一些基本概念講起。
確切地說,演算法是有限步驟的計算過程,該過程取某個值或集合作為輸入,並產生某個值或集合作為輸出。
如果演算法對於每個輸入都可以正確的停機,則稱該演算法是正確的,並稱正確的演算法解決了給定的計算問題。乙個不正確的演算法對於某個輸入可能根本不停機,也可能以不正確的結果停機,比如乙個排序問題:
輸入:乙個長度為n的陣列(a1,a2,a3...)
輸出:輸入陣列排序後的乙個陣列(b1,b2,b3....),滿足:b1≤b2≤b3
如果根據這個問題設計出來的演算法交給圖靈機執行輸出的結果與人們的期望相反(比如輸出:b3,b2,b1…),那麼這個演算法就是錯誤的。
針對同一計算問題,可以設計出多種演算法,其中有好有壞,我們可以通過**演算法需要的資源來篩選出好的演算法。雖然有時我們關心記憶體、頻寬這類硬體資源,但通常我們度量的是執行時間。
度量演算法的執行時間,人們通常用的是隨機訪問機模型(random-access machine, ram)。在 ram 模型中:
ram 模型假定的觀點是,執行每行偽**所需的時間是乙個常量時間,雖然真實計算機執行一行**與另一行**需要不同的常量時間。依此,乙個演算法在特定輸入上的執行時間不是指現實意義的時間,而是執行指令的次數。
比如下面這段 foo 函式的**:
// prettier-ignore
function
foo(n)
return
0// 1 次
}
上面的**需要執行 2n + 1 + n + 1 = 3n + 2 次指令,也就是說執行時間是 3n + 2,如果用乙個時間函式來表示,就是 t(n) = 3n + 2。
根據 ram 模型,乙個演算法可以在給定的輸入規模 n 下分析出乙個執行時間的函式 t(n)。研究 t(n) 常用的一種策略是分析輸入規模 n 增大的情況下 t(n) 的變化(如線性增長、指數增長等)。如果用 f(n) 來表示 t(n) 的增長速度,那麼 f(n) 和 t(n) 的關係我們約定用乙個大 o 來表示,即:
t(n) = o(f(n))這就是大 o 表示法。由於輸入規模 n 的增長率與 f(n) 的增長率是正相關的,所以稱作漸近時間複雜度(asymptotic time complexity),簡稱時間複雜度。相對應的,還有空間複雜度,這裡我們不作討論。
當 n 足夠大時或趨於無窮大時,t(n) 的常數部分就變得不重要,我們真正關心的是執行時間的增長量級或增長率。如果用 f(n) 來表示增長數度,上面 foo 示例**的增長速度可以表示為 f(n) = n,把它代入到 t(n) = o(f(n)) 就是:
t(n) = o(n)這時,我們稱 foo 的時間複雜度為 o(n)。
常見的時間複雜度有:
隨著問題規模 n 的不斷增大,上述時間複雜度不斷增大,演算法的執行效率也越低。大 o 表示法只是一種估算,當輸入規模足夠大的時候才有意義。
注意,大 o 表示法考慮的是最壞的情況。比如,從乙個長度為 n 的陣列中找乙個值等於 10 的元素,開始遍歷掃瞄這個陣列,有可能第 1 次就掃到了,也有可能是第 n 次才掃到。這裡最壞的情況是 n 次,所以時間複雜度就是 o(n)。
大部分情況下你用直覺就可以知道乙個演算法的大 o 表示。比如,如果用乙個迴圈遍歷輸入的每個元素,那麼這個演算法就是 $ o(n) $;如果是用迴圈套迴圈,那就是 $ o(n^2) $,以此類推。
參考:《演算法導論,第三版》
排序演算法相關
1.1氣泡排序 氣泡排序的基本原理是 依次比較相鄰的兩個數,將大數放在前面,小數放在後面,也即首先比較第乙個和第二個數,將大數放在前面,小數放在後面。然後比較第2個數和第3個數,將大數放前,小數方後,依次直至比較最後兩個數。如此在第一輪最後的數必定是所有數中最小的,然後重複以上過程將所有小數放在最後...
雜湊演算法相關梳理
1.基本概念 雜湊也成為 雜湊 任意長度的輸入,經過雜湊演算法變換成固定長度的輸出。雜湊值會產生碰撞,因為固定長度的雜湊值表示的範圍是有限的,但輸入值的範圍是無限的。通過原始資料通過相同的雜湊演算法可以計算出相同的雜湊值,但是通過雜湊值無法恢復原始資料 不同的雜湊演算法,雜湊值長度不同,但相同的雜湊...
演算法相關概念解釋
演算法 演算法是解決特定問題求解步驟的描述,在計算機中表現為指令的有限序列,並且每條指令表示乙個或多個操作。演算法的特性 有窮性,確定性,可行性,輸入,輸出。演算法的設計要求 正確性,可讀性,健壯性,高效率和低儲存。演算法的度量方法 事後統計法 不科學,不準確 事前分析估計法。輸入輸出 演算法有零個...