《算法之道》精華 經典算法部分
- 本書作者鄒恆明,作者另有一本書《數據結構之弦》,以及《操作系統之哲學原理》都是很好的書
- 這本書可以算得上是深入淺出,文筆很好,作者添加了很多自己的思考
- 本文包括經典算法部分
第十章 排序與次序
- 插入排序
- 從無序部分抽取一張插入有序部分
- 爲原地排序,無需佔用臨時存儲空間
- 最優情況下爲O(n),平均O(n^2)
- 折半插入排序
- 插入時使用二分查找
- 歸併排序
- 分治,從中間分解,分別排序後進行仔細的合併
- 異地排序,需要佔用額外空間
- n>=30時性能比插入排序更好。複雜度固定爲O(nlog(n))
- 快排
- 分治,複雜的部分在於分解,而歸併複雜在於合併
- 原地排序
- 最壞情況爲O(n^2),但只要不是每次都是最壞,複雜度就不是n^2,具有韌性
- 任何基於比較的排序,決策樹高度至少爲nlog(n)
- 計數排序
- 元素值範圍必須有限
- 空間複雜度高
- O(n)
- 基數排序
- 從最低位到最高位排序,每一位排序都採用穩定排序,如計數排序
- 一位排序應該選擇log(n)個比特,使整體成本最低
- 桶排序
- 把n元素按值分到n個桶裏,每個桶內部進行插入排序,將各桶首位相連
- 元素應該是均勻分佈
- 快速次序選擇:求第K大的數
- 使用快排的partition
- 最差O(n^2),平均O(n)
- 線性最差快速次序選擇
- 將元素每5個一組,分別取中值。在n/5箇中值裏面找到中值,作爲partition的pivot
- 爲什麼*不每3個一組?
- 保證pivot左邊右邊至少3n/10個元素
- 最差O(n)
第十一章 搜索與散列
- 順序搜索
- 在序列裏面如果搜索頻率從頭到尾指數遞減,則爲O(1)
- 折半搜索
- 對於有序序列,爲O(logn)
- 常數搜索:散列搜索
- 直接散列:非常簡單,不會發生碰撞,空間浪費大
- 除法(模除法)散列
- 元素對散列表大小m取模得到
- m必須爲素數,否則造成不均勻散射。比如m包含因子d,而大部分元素對d餘數相等
- m不能靠近2的冪。如m爲2的冪,散列結果將不依賴元素的所有位。靠近也不行,爲什麼?
- 乘法散列
- h(k) = (A * k ) % 2^r >> (w - r),w爲計算機字寬,A爲2^(w-1)與2^w之間的一個奇數
- 乘方取中法:乘方n次(常取n=2),取中間r位
- 開放尋址散列:散列碰撞時縱深擴展,添加一個鏈表
- 平均搜索時間爲O(1+a),a爲加載因子
- 封閉尋址散列:散列碰撞時爲元素找到另一個位置
- 找另一個位置的操作稱爲探尋
- 線性探尋
h(k,i) = (h'(k) + i) % m
,h'(k)爲家位- 向單方向尋找未被佔用的位置
- 易出現頂級聚集
- 非線性探尋
- 平方探尋
h(k,i) = (h'(k) + c1 * i + c2 * i^2) % m
易出現次級聚集
- 平方探尋
- 雙重散列探尋
- 使用兩個散列函數h1、h2來構造新散列函數
h(k,i) = (h1(k) + i * h2(k) ) mod m
- 僞隨機探尋
- 使用僞隨機序列
- 存在次級聚集
- 不成功搜索的探尋次數期望爲
1/(1-a)
- 成功搜索探尋次數最多爲
1 / a * ln( 1/(1-a))
- 封閉散列不能刪除元素,可以放標記解決。如果插入相比搜索非常稀疏,則可以通過重新散列解決空位問題
- 隨機化散列
- 找到一組散列函數,每次隨機選擇一個不同的散列函數
- 用於避免單個散列函數極端情況下聚集效應嚴重
- 全域散列
- 一組H個散列函數,將任意兩個不同的元素映射到同一位置的函數個數爲H/m
- 完美散列
- n個元素,構造m=O(n)大小的散列表,使搜索最壞達到O(1)
- 採用雙層散列,第一層大小n,第二層每個表的大小爲落到第一層位置i上的元素個數的平方
- 空間消耗爲O(n)
第十二章 最短路徑
- 如果圖中有負環,則不存在最短路徑
- 單源多點最短路徑
- Dijkstra算法
- 貪婪算法,要求不存在負路徑
- 最優子結構:最短路徑裏的每一段都是兩點之間的最短路徑
- 貪婪選擇屬性:路徑向外延伸的下一個節點就是離源點最近的節點
- 每次選取離源點最近的節點,更新所有與此節點相鄰節點的距離
- 時間複雜度爲O(V^2),採用堆實現,可以達到O(E log(V))。與Prim算法相同
- Bellman-Ford算法
- 可以應對負權重
- 進行V-1輪降距,每次更新圖中所有邊
- 複雜度爲O(VE)
- BFS
- 各邊權重相等的情況
- O(V+E)
- Dijkstra算法
- 多源多點最短路徑
- Floyd-Warshall算法
- 動態規劃算法
- 子問題爲從i到j,中間結點只屬於集合1...k的最短路徑長度
- c_ijk = min{c_ij(k-1), c_ik(k-1) + ckj(k-1)}|k
- 複雜度O(n^3)
- Jonhson算法
- 等效變換爲無負權重的圖,使用Dijkstra算法
- 添加一個節點s,到所有點路徑長度爲0,運行Bellman-Ford算法,對節點賦值
- 對每個節點運行Dijkstra算法
- 複雜度主要是Dijkstra算法運算,爲O(VE + V^2 log(V))
- 若Bellman-Ford算法報告有負環存在,不能使用此方法
- Floyd-Warshall算法