數據結構與算法分析(六)——算法設計技巧

從算法的實現向算法的設計轉變,提供解決問題的思路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 }  
View Code

最優二叉查找樹:元素帶權重(一般是出現的機率),目標是使得查找帶來的開銷最小,此時平衡二叉樹不是最優。與矩陣連乘問題相似,子問題分解公式爲:

                                      

全部點最短路徑: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 }  
View Code

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 }  
View Code

 

轉自:http://blog.csdn.net/woshishuizzz/article/details/8440309

相關文章
相關標籤/搜索