從算法的實現向算法的設計轉變,提供解決問題的思路ios
1.貪心算法算法
一種局部最優算法設計思路,思想是保證每一步選擇在當前達到最優。一個很常見的貪心算法案例是零錢找取問題。數據結構
調度問題:書上的調度問題比較簡單,其目標是全部做業的平均持續時間(調度+運行)最短,不管是但處理器仍是多處理器,最優解的方案老是按做業的長短排序進行調度。《計算機算法設計與分析》上的做業調度的目標是最後完成時間最小,這要稍微複雜一些。ide
霍夫曼編碼:最優編碼一定保持滿樹的性質,基本問題在於找到總之最小的滿二叉樹。最簡單的霍夫曼編碼是一個兩趟掃描算法:第一趟蒐集頻率數據,第二趟進行編碼。性能
近似裝箱問題:bin packing problem(不一樣於揹包),分爲兩類:聯機裝箱(on-line packing)和脫機裝箱(off-line packing),區別是脫機裝箱具備先驗性,而聯機裝箱不知道下一個到來的元素大小和總元素數目。測試
-- 能夠證實,聯機算法不能總給出最優解,也能夠證實,存在一些輸入使得任意聯機裝箱算法至少是最優箱子的4/3。有三種簡單的算法能夠保證最多使用兩倍的最優裝箱數。優化
1)丅項適配(next fit):任意一個物品到來時,只檢測當前箱子可否裝下,線性時間運行。ui
2)首次適配(first fit):依次掃描以前的箱子,尋找能放下當前物品的第一個箱子,O(N*N)運行(能夠優化),能夠證實,首次適配使用的箱子很少於1.7M(上取整)。編碼
3)最佳適配(best fit):將物品放到全部箱子中可以容納它的最滿的箱子中,不會超過1.7M。spa
-- 脫機算法:首次適配遞減算法(fist fit decreasing),按照非增進行排序,而後進行fist fit裝箱。
-- 應用:操做系統,在動態向堆申請內存的過程當中,以隱式空閒鏈表實現的分配器放置分配的塊時所採起的策略。(《深刻理解計算機系統》修訂版 P635)
2.分治算法
分治算法要求最優子結構 和獨立(非重疊)子問題,算法一般由兩部分組成divide-conquer。思路是遞歸解決較小的子問題,而後從子問題的解構建原問題的解。分治算法一般至少含有兩個內部遞歸,其運行時間可經過下面的關係獲得:
最近點問題:naive算法花費O(N*N)時間複雜度,能夠將點空間劃分爲兩半,遞歸的解兩個子問題,而後進行合併。須要注意的是,合併的複雜度決定了最終的複雜度,因此要對合並的過程進行優化。思路是,首先取左右區域最小值的最小值dmin=min(dleft_min, dright_min),做爲中間區域的邊界,橫軸界爲[middle-dmin, mindle+dmin],縱軸自上而下以高度爲dmin的窗口進行掃描,按照dmin的定義,窗口內待比較的元素不會超過7個,保證合併過程的線性複雜度,最終複雜度爲O(NlogN)。算法執行前需進行O(NlogN)預處理,保留兩個分別按照x座標和y座標排序的點的表P和Q。
選擇問題:利用快排實現的選擇問題能夠在平均時間O(N)下進行,但其最壞複雜度爲O(N*N),即便是使用了三元素中值樞紐元,也不能提供避免最壞狀況的保證。改進思路是從中項的樣本中找出中項(五分化中項的中項,media-of-media-of-five partitioning):將N個元素分紅N/5(上取整)組,找出每組的中項,而後找出中項的中項做爲樞紐元返回。能夠證實:1)每一個遞歸子問題的大小最可能是原問題的70%,這樣基本保證了快速選擇的問題等分;2)用8次比較能夠對5個數進行排序,而後遞歸的調用選擇算法找出中項的中項,因此尋找樞紐元的複雜度爲O(N),最終的複雜度也爲O(N)。
最後,五分化中項的中項方法系統開銷太大,根本不實用。
整數相乘:不只要進行子問題的劃分,還要經過合併變換減小實際的乘法次數
矩陣相乘:同上。
3.動態規劃
一般遞歸算法會重複一些子問題,形成沒必要要的性能降低。動態規劃的思路是棄用遞歸,將子問題的答案系統的記錄在中間表(table)中。其特色是最優子結構和重疊子問題。
矩陣連乘:尋找最優分割點。上式表示存在的組合數目,是一個指數形式,遍歷開銷太大;下式是對父問題的分解,注意Cleft-1。
算法實現:(數據大小很容易超出類型限制,注意比較邊界)
1 #include "stdafx.h"
2 #include <iostream>
3 #include <vector>
4 #include "matrix.h" //自定義matrix類
5 using namespace std; 6
7 #define INFINITY 999999999
8
9 void optMatrix(const vector<int> &c, matrix<long> &m, matrix<long> &lastChange) 10 { 11 //c[0]存儲第一個矩陣的行數,剩下的分別存儲第i個矩陣的列數
12 int n = c.size() - 1; 13 //將對角元素初始化爲0,保護邊界
14 for(int i = 1; i <= n; i++) 15 m.data[i][i] = 0; 16 //k = right - left,left和right最大間隔是n-1
17 for(int k = 1; k < n; k++) 18 for(int left = 1; left <= n-k; left++) 19 { 20 int right = left + k; 21 m.data[left][right] = INFINITY; 22 for(int i = left; i < right; i++) 23 { 24 long currentCost = m.data[left][i] + m.data[i+1][right] 25 + c[left-1]*c[i]*c[right]; 26 if(currentCost < m.data[left][right]) 27 { 28 m.data[left][right] = currentCost; 29 lastChange.data[left][right] = i; 30 } 31 } 32 } 33 }
最優二叉查找樹:元素帶權重(一般是出現的機率),目標是使得查找帶來的開銷最小,此時平衡二叉樹不是最優。與矩陣連乘問題相似,子問題分解公式爲:
全部點最短路徑:Dijkstra算法對於單源最短路徑查找爲O(N*N),全部點須要N次迭代。此處給出一種更緊湊的動態規劃算法,該算法支持負值路徑(Dijkstra不支持)。子問題分解式:
算法實現:
1 void dynamicDij(matrix<int> &a, matrix<int> &d, matrix<int> &route) 2 { 3 //matrix<int> &a不能定義成const,不然調用不了getHeight()
4 int n = a.getHeight(); 5
6 for(int i = 0; i < n; i++) 7 for(int j = 0; j < n; j++) 8 { 9 d.data[i][j] = a.data[i][j]; 10 route.data[i][j] = -1; 11 } 12
13 for(int k = 0; k < n; k++) 14 for(int i = 1; i < n; i++) 15 for(int j = 0; j < n; j++) 16 { 17 int cost_tmp = d.data[i][k]+d.data[k][j]; 18 if(cost_tmp < d.data[i][j]) 19 { 20 d.data[i][j] = cost_tmp; 21 route.data[i][j] = k; 22 } 23 } 24 }
4. 隨機化算法
好的隨機化算法沒有很差的輸入,而只有很差的隨機數(考慮快排樞紐元的選取)。
隨機數生成器:實現真正的隨機數生成器是不可能的,依賴於算法,都是些僞隨機數。產生隨機數最簡答的方法是線性同餘生成器,Xi+1 = A Xi mod M,序列的最大生成周期爲M-1,還須要仔細選擇A,貿然修改一般意味着失敗。須要注意的是,直接按照前面公式實現有可能發生溢出大數的現象,一般須要進行變換。
跳躍表:藉助隨機化算法實現以O(NlogN)指望時間支持查找和插入操做數據結構。本質是多指針鏈表。當查找時,從跳躍表節點的最高階鏈開始尋找;當插入元素時,其階數是隨機的(拋硬幣直到正面朝上時的總次數)。
素性測試:某些密碼方案依賴於大數分解的難度。費馬小定理(下1):若是定理宣稱一個數不是素數,那這個數確定不是素數;若宣稱是素數的話,有可能不是素數。可藉助平方探測的特殊狀況(下2)加以判斷。
5.回溯算法
雖然分析回溯算法的複雜度可能很高,但實際上的性能卻很好,明顯優於窮舉法。有時還會藉助裁剪的方法進一步下降複雜度。與分支限界法不一樣,回溯算法一般是深度優先遞歸的搜索全部解,而分支限界一般是廣度優先,找出知足條件的一個解。
公路收費問題:(重構問題遠比建立問題複雜)通常以O(N*NlogN)運行(假設沒有回溯,那麼以優先隊列實現d,每次插取數據logN,共插取了N*N次),最壞需花費指數時間。僞代碼:
1 bool tuinpike(vector<int> &x, DisSet d, int n) 2 { 3 x[1] = 0; //設置原點
4 d.deleteMax(x[n]); 5 d.deleteMax(x[n-1]); 6
7 //因爲問題在初始時具備對稱性,所以判斷一次便可
8 if(x[n] - x[n-1] in d) 9 { 10 d.remove(x[n] - x[n-1]); 11 return place(x, d, n, 2, n - 2); 12 } 13 else
14 return false; 15 } 16
17 bool place(vector<int> &x, DisSet d, int n, int left, int right) 18 { 19 bool found = false; 20
21 if (d.isEmpty()) return ture; 22
23 int max = d.findMax(); 24
25 if(|x[i] - max| in d, for all 1<=i<left, right<i<=n) 26 { 27 x[right] = max; 28 for(1<=i<left, right<i<=n) 29 d.remove(|x[i] - max|); 30 found = place(x, d, n, left, right - 1); 31
32 //backtrack不可行,恢復問題,換個方向繼續試
33 if(found == false) 34 { 35 for(1<=i<left, right<i<=n) 36 d.insert(|x[i] - max|); 37 } 38 } 39
40 if(found == false && |x[i] - (x[n] - max)| in d, for all 1<=i<left, right<i<=n ) 41 { 42 x[left] = x[n] - max; //注意不是max
43 for(1<=i<left, right<i<=n) 44 d.remove(|x[i] - (x[n] - max)|); 45 found = place(x, d, n, left + 1, right); 46
47 if(found == false) 48 { 49 for(1<=i<left, right<i<=n) 50 d.insert(|x[i] - (x[n] - max)|); 51 } 52 } 53
54 return found; 55 }
轉自:http://blog.csdn.net/woshishuizzz/article/details/8440309