啓發式算法

什麼是啓發式算法

啓發式算法一般用於解決NP-hard問題,其中NP是指非確定性多項式。

啓發式算法(Heuristic Algorithm)有不同的定義:一種定義爲,一個基於直觀或經驗的構造的算法,對優化問題的實例能給出可接受的計算成本(計算時間、佔用空間等)內,給出一個近似最優解,該近似解於真實最優解的偏離程度不一定可以事先預計;另一種是,啓發式算法是一種技術,這種技術使得在可接受的計算成本內去搜尋最好的解,但不一定能保證所得的可行解和最優解,甚至在多數情況下,無法闡述所得解同最優解的近似程度。我比較贊同第二種定義,因爲啓發式算法現在還沒有完備的理論體系,只能視作一種技術。

例如,著名的推銷員旅行問題(Travel Saleman Problem or TSP):假設一個推銷員需要從南京出發,經過廣州,北京,上海,…,等 n 個城市, 最後返回香港。 任意兩個城市之間都有飛機直達,但票價不等。假設公司只給報銷 C 元錢,問是否存在一個行程安排,使得他能遍歷所有城市,而且總的路費小於 C?

推銷員旅行問題顯然是 NP 的。因爲如果你任意給出一個行程安排,可以很容易算出旅行總開銷。但是,要想知道一條總路費小於 C 的行程是否存在,在最壞情況下,必須檢查所有可能的旅行安排。

啓發式算法是相對於最優化算法提出的,是基於直觀或者經驗構造的算法,在可接受的開銷(時間和空間)內給出待解決組合優化問題的一個可行解。

目前通用的啓發式算法

目前比較通用的啓發式算法一般有模擬退火算法(SA)、遺傳算法(GA)、蟻羣算法(ACO)、人工神經網絡(ANN)等。

模擬退火算法(SA)

模擬退火算法(Simulated Annealing, SA)的思想借鑑於固體的退火原理,當固體的溫度很高的時候,內能比較大,固體的內部粒子處於快速無序運動,當溫度慢慢降低的過程中,固體的內能減小,粒子的慢慢趨於有序,最終,當固體處於常溫時,內能達到最小,此時,粒子最爲穩定。模擬退火算法便是基於這樣的原理設計而成。

模擬退火算法步驟

  1. 初始化溫度T(充分大),溫度下限Tmin(充分小),初始解X,每個T值迭代次數L

  2. 隨機生成臨域解x_new;

  3. 設f(x)函數來計算用來計算解得好壞,計算出f(x_new)-f(x);

  4. 如果f(x_new)-f(x)>0,說明新解比原來的解好,則無條件接受,如果f(x_new)-f(x)<0,則說明舊解比新解好,則以概率exp((f(xnew)-f(x))/k*T)接受x_new作爲解。

  5. 如果當前溫度<Tmin時,則退出循環,輸出當前結果,否則減少當前溫度,回到第2步繼續循環,常用的降溫方法爲T= a*T (0<a<1),一般a取接近1的值

模擬退火算法實例

求解函數最小值問題:​ 其中,0<=x<=100,給定任意y值,求x爲多少時,F(x)最小。

  1. public class SATest {
  2. public static final int T = 1000;// 初始化溫度
  3. public static final double Tmin = 1;// 溫度的下界
  4. public static final int k = 100;// 迭代的次數
  5. public static final double delta = 0.98;// 溫度的下降率
  6. public static double getX() {
  7. return Math.random() * 100;
  8. }
  9. /**
  10. * 評價函數的值,即對應上文中的f(x)
  11. *
  12. * @param x目標函數中的一個參數
  13. * @param y目標函數中的另一個參數
  14. * @return函數值
  15. */
  16. public static double getFuncResult(double x, double y) {
  17. double result = 6 * Math.pow(x, 7) + 8 * Math.pow(x, 6) + 7
  18. * Math.pow(x, 3) + 5 * Math.pow(x, 2) - x * y;
  19. return result;
  20. }
  21. /**
  22. * 模擬退火算法的過程
  23. * @param y目標函數中的指定的參數
  24. * @return最優解
  25. */
  26. public static double getSA(double y) {
  27. double result = Double.MAX_VALUE;// 初始化最終的結果
  28. double x[] = new double[k];
  29. // 初始化初始解
  30. for (int i = 0; i < k; i++) {
  31. x[i] = getX();
  32. }
  33. // 迭代的過程
  34. while (t > Tmin) {
  35. for (int i = 0; i < k; i++) {
  36. // 計算此時的函數結果
  37. double funTmp = getFuncResult(x[i], y);
  38. // 在鄰域內產生新的解
  39. double x_new = x[i] + (Math.random() * 2 - 1);
  40. // 判斷新的x不能超出界
  41. if (x_new >= 0 && x_new <= 100) {
  42. double funTmp_new = getFuncResult(x_new, y);
  43. if (funTmp_new - funTmp < 0) {
  44. // 替換
  45. x[i] = x_new;
  46. } else {
  47. // 以概率替換
  48. double p = 1 / (1 + Math
  49. .exp(-(funTmp_new - funTmp) / T));
  50. if (Math.random() < p) {
  51. x[i] = x_new;
  52. }
  53. }
  54. }
  55. }
  56. T = T * delta;
  57. }
  58. for (int i = 0; i < k; i++) {
  59. result = Math.min(result, getFuncResult(x[i], y));
  60. }
  61. return result;
  62. }
  63. public static void main(String args[]) {
  64. // 設置y的值
  65. int y = 0;
  66. System.out.println("最優解爲:" + getSA(y));
  67. }
  68. }

遺傳算法(GA)

遺傳算法(Genetic Algorithm, GA)起源於對生物系統所進行的計算機模擬研究。它是模仿自然界生物進化機制發展起來的隨機全局搜索和優化方法,借鑑了達爾文的進化論和孟德爾的遺傳學說。其本質是一種高效、並行、全局搜索的方法,能在搜索過程中自動獲取和積累有關搜索空間的知識,並自適應地控制搜索過程以求得最佳解。

相關術語

編碼(coding):將物體的表現型用編碼的方式轉爲程序可控的基因型。

  1. 比如現在要計算北京、天津、廣東、新疆這四個城市的一條最優路徑,但算法程序不能夠直接處理北京、天津、廣東、新疆這些數據,所以我們得給 它們編上號,北京(0)、天津(1)、廣東(2)、新疆(3),路徑(天津->新疆->北京->廣東)可以表示成基因型串結構數據 (1302),這樣算法程序只要直接處理它們的編號就行了。
  2. (1)二進制編碼,基因用0或1表示(常用於解決01揹包問題)
  3. 如:基因A:00100011010 (代表一個個體的染色體)
  4. (2)互換編碼(用於解決排序問題,如旅行商問題和調度問題)
  5. 如旅行商問題中,一串基因編碼用來表示遍歷的城市順序,如:234517986,表示九個城市中,先經過城市2,再經過城市3,依此類推。

解碼(decoding):基因型到表現型的映射。

基因型(genotype):參數的因子;

表現型(phenotype):根據不同因子最終展現的形態;

適應度(fitness):度量某個結果的好壞。

進化(evolution):不斷剔除差的結果,最終逐步留下好的結果。

選擇(selection):以一定的概率從種羣中選擇若干個個體留下,並繁殖。選擇過程是一種基於適應度的優勝劣汰的過程。

複製(reproduction):將父本、母本的基因複製,以便產生下一代。

交叉(crossover):兩個染色體的某一相同位置處DNA被切斷,前後兩串分別交叉組合形成兩個新的染色體。也稱基因重組或雜交;

  1. 1)單交叉點法 (用於二進制編碼)
  2. 選擇一個交叉點,子代在交叉點前面的基因從一個父代基因那裏得到,後面的部分從另外一個父代基因那裏得到。
  3. 如:交叉前:
  4. 00000|01110000000010000
  5. 11100|00000111111000101
  6. 交叉後:
  7. 00000|00000111111000101
  8. 11100|01110000000010000
  9. 2)雙交叉點法 (用於二進制編碼)
  10. 選擇兩個交叉點,子代基因在兩個交叉點間部分來自一個父代基因,其餘部分來自於另外一個父代基因.
  11. 如:交叉前:
  12. 01 |0010| 11
  13. 11 |0111| 01
  14. 交叉後:
  15. 11 |0010| 01
  16. 01 |0111| 11
  17. 3)基於“ 與/或 ”交叉法 (用於二進制編碼)
  18. 對父代按位"與”邏輯運算產生一子代A;按位”或”邏輯運算產生另一子代B。該交叉策略在解揹包問題中效果較好 .
  19. 如:交叉前:
  20. 01001011
  21. 11011101
  22. 交叉後:
  23. 01001001
  24. 11011111
  25. (4)單交叉點法 (用於互換編碼)
  26. 選擇一個交叉點,子代的從初始位置出發的部分從一個基因複製,然後在另一個基因中掃描,如果某個位點在子代中沒有,就把它添加進去。
  27. 如:交叉前:
  28. 87213 | 09546
  29. 98356 | 71420
  30. 交叉後:
  31. 87213 | 95640
  32. 98356 | 72104
  33. (5)部分匹配交叉(PMX)法(用於互換編碼)
  34. 先隨機產生兩個交叉點,定義這兩點間的區域爲匹配區域,並用交換兩個父代的匹配區域。
  35. 父代A:872 | 130 | 9546
  36. 父代B:983 | 567 | 1420 變爲:
  37. TEMP A: 872 | 567 | 9546
  38. TEMP B: 983 | 130 | 1420
  39. 對於 TEMP A、TEMP B中匹配區域以外出現的數碼重複,要依據匹配區域內的位置逐一進行替換。匹配關係:1<——>5 3<——>6 7<——>0
  40. 子代A:802 | 567 | 9143
  41. 子代B:986 | 130 | 5427
  42. (6)順序交叉法(OX) (用於互換編碼)
  43. 從父代A隨機選一個編碼子串,放到子代A的對應位置;子代A空餘的位置從父代B中按B的順序選取(與己有編碼不重複)。同理可得子代B。
  44. 父代A: 872 | 139 | 0546
  45. 父代B: 983 | 567 | 1420
  46. 交叉後:
  47. 子代A: 856 | 139 | 7420
  48. 子代B: 821 | 567 | 3904
  49. (7)循環交叉(CX)(用於互換編碼)
  50. CX同OX交叉都是從一個親代中取一些城市,而其它城市來自另外一個親代,但是二者不同之處在於:OX中來自第一個親代的編碼子串是隨機產生的,而CX卻不是,它是根據兩個雙親相應位置的編碼而確定的。
  51. 父代A:1 2 3 4 5 6 7 8 9
  52. 父代B:5 4 6 9 2 3 7 8 1
  53. 可得循環基因:1->5->2->4->3->6->9->7->8
  54. 子代B的編碼同理。(循環基因 5->1->4->2->6->3->9->7->8)

變異(mutation):交叉後可能(很小的概率)對染色體進行更改,來防止算法過早收斂而陷入局部最優解中。

變異概率Pm不能太小,這樣降低全局搜索能力;也不能太大,Pm > 0.5,這時GA退化爲隨機搜索。

(1)基本位變異算子(用於二進制編碼)

基本位變異算子是指對個體編碼串隨機指定的某一位或某幾位基因作變異運算。對於基本遺傳算法中用二進制編碼符號串所表示的個體,若需要進行變異操作的某一基因座上的原有基因值爲0,則變異操作將其變爲1;反之,若原有基因值爲1,則變異操作將其變爲0。

變異前:

000001110000000010000

變異後:

000001110001000010000

(2)逆轉變異算子(用於互換編碼)(源代碼中使用類似此方法)

在個體中隨機挑選兩個逆轉點,再將兩個逆轉點間的基因交換。

變異前:

1346798205

變異後:

1246798305

個體(individual):指染色體帶有特徵的實體;

種羣(population):個體的集合,該集合內個體數稱爲種羣的大小

遺傳算法步驟

  1. 對潛在問題進行編碼,初始化基因組,並根據基因組隨機初始化種羣,並指定繁衍代數。

  2. 計算種羣中每個個體的適應度,選擇一定數目的留下,其餘淘汰。

  3. 在留下的個體中,隨機繁衍,對分母基因進行交叉(極小概率變異),產生下一代。

  4. 回到第2步進行循環。直到達到指定的繁衍代數

蟻羣算法(ACO)

簡單介紹一下蟻羣算法的思路。我們嘗試復原一下螞蟻尋找食物的場景。想象有一隻螞蟻找到了食物,這時它需要將食物帶回蟻穴。對於這一隻螞蟻而言,它顯然並不知道應該怎麼走。那麼,這隻螞蟻有可能會隨機選擇一條路線。

這條路線很可能是一條遠路。但是,螞蟻一路上留下了記號,也就是信息素。如果這隻螞蟻繼續不停地搬運食物,或者有許多其他螞蟻一塊搬運的話。他們總會在運氣好的時候走到更快往返的路線上。螞蟻選擇的路越好,相同時間內往返的次數也就更多,也就在路上留下了更多的信息素。

於是,螞蟻們總會發現,有一些路徑的信息素更濃,這些路徑就是更好的路線。於是螞蟻也就更多地向信息素更濃的路徑上偏移。螞蟻們不停重複這個過程,最終總能找到一條確定的路線,而這條路線就是螞蟻們找到的最優路徑。

蟻羣算法步驟

  1. 初始化螞蟻數量、可行路段、每條路段距離、每條路段的初始信息素大小等信息

  2. 設定螞蟻的起點、終點。

  3. 螞蟻從起點出髮根據信息素濃度,有一定的概率性選擇路段,濃度越高,概率越大,逐步回到終點。

  4. 在螞蟻走過的路徑上,根據每條路段的長度按比例釋放信息素,短的路段釋放的信息素多,長的路段釋放的信息素少。

  5. 對所有路段的信息素進行揮發。

  6. 回到第二步進行循環,直到螞蟻數量迭代完。



作者:夏蟲亦可語冰
鏈接:https://www.jianshu.com/p/e2aec624106a
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章