模擬退火算法是用來求解最優化問題的算法。比如著名的TSP問題,函數最大值最小值問題等等。接下來將以如下幾個方面來詳細介紹模擬退火算法。
Contents
1. 模擬退火算法認識
2. 模擬退火算法描述
3. 費馬點問題求解
4. 最小包含球問題求解
5. 函數最值問題求解
6. TSP問題求解
1. 模擬退火算法認識
爬山算法也是一個用來求解最優化問題的算法,每次都向着當前上升最快的方向往上爬,但是初始化不同可能
會得到不同的局部最優值,模擬退火算法就可能跳出這種局部最優解的限制。模擬退火算法是模擬熱力學系統
中的退火過程。在退火過程中是將目標函數作爲能量函數。大致過程如下
初始高溫 => 溫度緩慢下降=> 終止在低溫 (這時能量函數達到最小,目標函數最小)
在熱力學中的退火過程大致是變溫物體緩慢降溫而達到分子之間能量最低的狀態。設熱力學系統S中有有限個且
離散的n個狀態,狀態
的能量爲
,在溫度
下,經過一段時間達到熱平衡,這時處於狀態
的概率爲
![]()
模擬退火算法也是貪心算法,但是在其過程中引入了隨機因素,以一定的概率接受一個比當前解要差的解,並且
這個概率隨着時間的推移而逐漸降低。
2. 模擬退火算法描述
模擬退火算法來源於固體退火原理,將固體加溫至充分高,再讓其徐徐冷卻,加溫時,固體內部粒子隨溫升變爲無序狀,內能增大,而徐徐冷卻時粒子漸趨有序,在每個溫度都達到平衡態,最後在常溫時達到基態,內能減爲最小。
模擬退火算法(Simulated Annealing,SA)最早由Kirkpatrick等應用於組合優化領域,它是基於Monte-Carlo迭代求解策略的一種隨機尋優算法,其出發點是基於物理中固體物質的退火過程與一般組合優化問題之間的相似性。模擬退火算法從某一較高初溫出發,伴隨溫度參數的不斷下降,結合概率突跳特性在解空間中隨機尋找目標函數的全局最優解,即在局部最優解能概率性地跳出並最終趨於全局最優。模擬退火算法是一種通用的優化算法,理論上算法具有概率的全局優化性能,目前已在工程中得到了廣泛應用,諸如VLSI、生產調度、控制工程、機器學習、神經網絡、信號處理等領域。
模擬退火算法是通過賦予搜索過程一種時變且最終趨於零的概率突跳性,從而可有效避免陷入局部極小並最終趨於全局最優的串行結構的優化算法。
模擬退火算法的模型
模擬退火算法可以分解爲解空間、目標函數和初始解三部分。
模擬退火的基本思想:
(1) 初始化:初始溫度T(充分大),初始解狀態S(是算法迭代的起點), 每個T值的迭代次數L
(2) 對k=1,……,L做第(3)至第6步:
(3) 產生新解S′
(4) 計算增量Δt′=C(S′)-C(S),其中C(S)爲評價函數
(5) 若Δt′<0則接受S′作爲新的當前解,否則以概率exp(-Δt′/T)接受S′作爲新的當前解.
(6) 如果滿足終止條件則輸出當前解作爲最優解,結束程序。
終止條件通常取爲連續若干個新解都沒有被接受時終止算法。
(7) T逐漸減少,且T->0,然後轉第2步。
模擬退火算法流程圖
![Simulated_Annealing_flowchart Simulated_Annealing_flowchart]()
若
,即移動後得到更優的解,那麼總是接受改移動。
若
,即移動後得到更差的解,則以一定的概率接受該移動,並且這個概率隨時間推移
逐漸降低。這個概率表示爲
![]()
由於是退火過程,所以dE < 0,這個公式說明了溫度越高出現一次能量差爲dE的降溫概率就越大,溫度越低,
出現降溫的概率就越小,由於dE總是小於0,所以P(dE)取值在0到1之間。僞碼如下
![]()
3. 費馬點問題求解
題目:http://poj.org/problem?id=2420
題意:給n個點,找出一個點,使這個點到其他所有點的距離之和最小,也就是求費馬點。
代碼:
-
#include <iostream>
-
#include <string.h>
-
#include <stdlib.h>
-
#include <stdio.h>
-
#include <time.h>
-
#include <math.h>
-
-
#define N 1005
-
#define eps 1e-8 //搜索停止條件閥值
-
#define INF 1e99
-
#define delta 0.98 //溫度下降速度
-
#define T 100 //初始溫度
-
-
using namespace std;
-
-
int dx[4] = {0, 0, -1, 1};
-
int dy[4] = {-1, 1, 0, 0};
-
-
struct Point
-
{
-
double x, y;
-
};
-
-
Point p[N];
-
-
double dist(Point A, Point B)
-
{
-
return sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y));
-
}
-
-
double GetSum(Point p[], int n, Point t)
-
{
-
double ans = 0;
-
while(n--)
-
ans += dist(p[n], t);
-
return ans;
-
}
-
-
-
double Search(Point p[], int n)
-
{
-
Point s = p[0];
-
double t = T;
-
double ans = INF;
-
while(t > eps)
-
{
-
bool flag = 1;
-
while(flag)
-
{
-
flag = 0;
-
for(int i = 0; i < 4; i++)
-
{
-
Point z;
-
z.x = s.x + dx[i] * t;
-
z.y = s.y + dy[i] * t;
-
double tp = GetSum(p, n, z);
-
if(ans > tp)
-
{
-
ans = tp;
-
s = z;
-
flag = 1;
-
}
-
}
-
}
-
t *= delta;
-
}
-
return ans;
-
}
-
-
int main()
-
{
-
int n;
-
while(scanf("%d", &n) != EOF)
-
{
-
for(int i = 0; i < n; i++)
-
scanf("%lf %lf", &p[i].x, &p[i].y);
-
printf("%.0lf\n", Search(p, n));
-
}
-
return 0;
-
}
題目:平面上給定n條線段,找出一個點,使這個點到這n條線段的距離和最小。
代碼:
-
#include <iostream>
-
#include <string.h>
-
#include <stdlib.h>
-
#include <stdio.h>
-
#include <time.h>
-
#include <math.h>
-
-
#define N 1005
-
#define eps 1e-8 //搜索停止條件閥值
-
#define INF 1e99
-
#define delta 0.98 //溫度下降速度
-
#define T 100 //初始溫度
-
-
using namespace std;
-
-
int dx[4] = {0, 0, -1, 1};
-
int dy[4] = {-1, 1, 0, 0};
-
-
struct Point
-
{
-
double x, y;
-
};
-
-
Point s[N], t[N];
-
-
double cross(Point A, Point B, Point C)
-
{
-
return (B.x - A.x) * (C.y - A.y) - (B.y - A.y) * (C.x - A.x);
-
}
-
-
double multi(Point A, Point B, Point C)
-
{
-
return (B.x - A.x) * (C.x - A.x) + (B.y - A.y) * (C.y - A.y);
-
}
-
-
double dist(Point A, Point B)
-
{
-
return sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y));
-
}
-
-
double GetDist(Point A, Point B, Point C)
-
{
-
if(dist(A, B) < eps) return dist(B, C);
-
if(multi(A, B, C) < -eps) return dist(A, C);
-
if(multi(B, A, C) < -eps) return dist(B, C);
-
return fabs(cross(A, B, C) / dist(A, B));
-
}
-
-
double GetSum(Point s[], Point t[], int n, Point o)
-
{
-
double ans = 0;
-
while(n--)
-
ans += GetDist(s[n], t[n], o);
-
return ans;
-
}
-
-
double Search(Point s[], Point t[], int n, Point &o)
-
{
-
o = s[0];
-
double tem = T;
-
double ans = INF;
-
while(tem > eps)
-
{
-
bool flag = 1;
-
while(flag)
-
{
-
flag = 0;
-
for(int i = 0; i < 4; i++)
-
{
-
Point z;
-
z.x = o.x + dx[i] * tem;
-
z.y = o.y + dy[i] * tem;
-
double tp = GetSum(s, t, n, z);
-
if(ans > tp)
-
{
-
ans = tp;
-
o = z;
-
flag = 1;
-
}
-
}
-
}
-
tem *= delta;
-
}
-
return ans;
-
}
-
-
int main()
-
{
-
int n;
-
while(scanf("%d", &n) != EOF)
-
{
-
for(int i = 0; i < n; i++)
-
scanf("%lf %lf %lf %lf", &s[i].x, &s[i].y, &t[i].x, &t[i].y);
-
Point o;
-
double ans = Search(s, t, n, o);
-
printf("%.1lf %.1lf %.1lf\n", o.x, o.y, ans);
-
}
-
return 0;
-
}
4. 最小包含球問題求解
題目:http://poj.org/problem?id=2069
題意:給定三維空間的n點,找出一個半徑最小的球把這些點全部包圍住。
代碼:
-
#include <iostream>
-
#include <string.h>
-
#include <stdio.h>
-
#include <math.h>
-
-
#define N 55
-
#define eps 1e-7
-
#define T 100
-
#define delta 0.98
-
#define INF 1e99
-
-
using namespace std;
-
-
struct Point
-
{
-
double x, y, z;
-
};
-
-
Point p[N];
-
-
double dist(Point A, Point B)
-
{
-
return sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y) + (A.z - B.z) * (A.z - B.z));
-
}
-
-
double Search(Point p[], int n)
-
{
-
Point s = p[0];
-
double t = T;
-
double ans = INF;
-
while(t > eps)
-
{
-
int k = 0;
-
for(int i = 0; i < n; i++)
-
if(dist(s, p[i]) > dist(s, p[k]))
-
k = i;
-
double d = dist(s, p[k]);
-
ans = min(ans, d);
-
s.x += (p[k].x - s.x) / d * t;
-
s.y += (p[k].y - s.y) / d * t;
-
s.z += (p[k].z - s.z) / d * t;
-
t *= delta;
-
}
-
return ans;
-
}
-
-
int main()
-
{
-
int n;
-
while(cin >> n && n)
-
{
-
for(int i = 0; i < n; i++)
-
cin >> p[i].x >> p[i].y >> p[i].z;
-
double ans = Search(p, n);
-
printf("%.5lf\n", ans);
-
}
-
return 0;
-
}
5. 函數最值問題求解
題目:http://acm.hdu.edu.cn/showproblem.php?pid=2899
題意:給出方程
,其中
,輸入
,求
的最小值。
分析:本題可以用經典的二分法求解,這種方法比較簡單,就不說了。主要來說模擬退火做法。
代碼:
-
#include <iostream>
-
#include <string.h>
-
#include <stdlib.h>
-
#include <algorithm>
-
#include <stdio.h>
-
#include <time.h>
-
#include <math.h>
-
-
#define ITERS 10
-
#define T 100
-
#define eps 1e-8
-
#define delta 0.98
-
#define INF 1e99
-
-
using namespace std;
-
-
double x[ITERS];
-
-
int Judge(double x, double y)
-
{
-
if(fabs(x - y) < eps) return 0;
-
return x < y ? -1 : 1;
-
}
-
-
double Random()
-
{
-
double x = rand() * 1.0 / RAND_MAX;
-
if(rand() & 1) x *= -1;
-
return x;
-
}
-
-
double F(double x, double y)
-
{
-
return 6 * x * x * x * x * x * x * x + 8 * x * x * x * x * x * x + 7 * x * x * x + 5 * x * x - y * x;
-
}
-
-
void Init()
-
{
-
for(int i = 0; i < ITERS; i++)
-
x[i] = fabs(Random()) * 100;
-
}
-
-
double SA(double y)
-
{
-
double ans = INF;
-
double t = T;
-
while(t > eps)
-
{
-
for(int i = 0; i < ITERS; i++)
-
{
-
double tmp = F(x[i], y);
-
for(int j = 0; j < ITERS; j++)
-
{
-
double _x = x[i] + Random() * t;
-
if(Judge(_x, 0) >= 0 && Judge(_x, 100) <= 0)
-
{
-
double f = F(_x, y);
-
if(tmp > f)
-
x[i] = _x;
-
}
-
}
-
}
-
t *= delta;
-
}
-
for(int i = 0; i < ITERS; i++)
-
ans = min(ans, F(x[i], y));
-
return ans;
-
}
-
-
int main()
-
{
-
int t;
-
scanf("%d", &t);
-
while(t--)
-
{
-
double y;
-
scanf("%lf", &y);
-
srand(time(NULL));
-
Init();
-
printf("%.4lf\n", SA(y));
-
}
-
return 0;
-
}
6. TSP問題求解
TSP問題是一個NP問題,但是可以求近似解,通過模擬退火算法實現,代碼如下
代碼:
-
#include <iostream>
-
#include <string.h>
-
#include <stdlib.h>
-
#include <algorithm>
-
#include <stdio.h>
-
#include <time.h>
-
#include <math.h>
-
-
#define N 30 //城市數量
-
#define T 3000 //初始溫度
-
#define EPS 1e-8 //終止溫度
-
#define DELTA 0.98 //溫度衰減率
-
#define LIMIT 10000 //概率選擇上限
-
#define OLOOP 1000 //外循環次數
-
#define ILOOP 15000 //內循環次數
-
-
using namespace std;
-
-
-
struct Path
-
{
-
int citys[N];
-
double len;
-
};
-
-
-
struct Point
-
{
-
double x, y;
-
};
-
-
Path path;
-
Point p[N];
-
double w[N][N];
-
int nCase;
-
-
double dist(Point A, Point B)
-
{
-
return sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y));
-
}
-
-
void GetDist(Point p[], int n)
-
{
-
for(int i = 0; i < n; i++)
-
for(int j = i + 1; j < n; j++)
-
w[i][j] = w[j][i] = dist(p[i], p[j]);
-
}
-
-
void Input(Point p[], int &n)
-
{
-
scanf("%d", &n);
-
for(int i = 0; i < n; i++)
-
scanf("%lf %lf", &p[i].x, &p[i].y);
-
}
-
-
void Init(int n)
-
{
-
nCase = 0;
-
path.len = 0;
-
for(int i = 0; i < n; i++)
-
{
-
path.citys[i] = i;
-
if(i != n - 1)
-
{
-
printf("%d--->", i);
-
path.len += w[i][i + 1];
-
}
-
else
-
printf("%d\n", i);
-
}
-
printf("\nInit path length is : %.3lf\n", path.len);
-
}
-
-
void Print(Path t, int n)
-
{
-
printf("Path is : ");
-
for(int i = 0; i < n; i++)
-
{
-
if(i != n - 1)
-
printf("%d-->", t.citys[i]);
-
else
-
printf("%d\n", t.citys[i]);
-
}
-
printf("\nThe path length is : %.3lf\n", t.len);
-
}
-
-
Path GetNext(Path p, int n)
-
{
-
Path ans = p;
-
int x = (int)(n * (rand() / (RAND_MAX + 1.0)));
-
int y = (int)(n * (rand() / (RAND_MAX + 1.0)));
-
while(x == y)
-
{
-
x = (int)(n * (rand() / (RAND_MAX + 1.0)));
-
y = (int)(n * (rand() / (RAND_MAX + 1.0)));
-
}
-
swap(ans.citys[x], ans.citys[y]);
-
ans.len = 0;
-
for(int i = 0; i < n - 1; i++)
-
ans.len += w[ans.citys[i]][ans.citys[i + 1]];
-
cout << "nCase = " << nCase << endl;
-
Print(ans, n);
-
nCase++;
-
return ans;
-
}
-
-
void SA(int n)
-
{
-
double t = T;
-
srand(time(NULL));
-
Path curPath = path;
-
Path newPath = path;
-
int P_L = 0;
-
int P_F = 0;
-
while(1)
-
{
-
for(int i = 0; i < ILOOP; i++)
-
{
-
newPath = GetNext(curPath, n);
-
double dE = newPath.len - curPath.len;
-
if(dE < 0)
-
{
-
curPath = newPath;
-
P_L = 0;
-
P_F = 0;
-
}
-
else
-
{
-
double rd = rand() / (RAND_MAX + 1.0);
-
if(exp(dE / t) > rd && exp(dE / t) < 1)
-
curPath = newPath;
-
P_L++;
-
}
-
if(P_L > LIMIT)
-
{
-
P_F++;
-
break;
-
}
-
}
-
if(curPath.len < newPath.len)
-
path = curPath;
-
if(P_F > OLOOP || t < EPS)
-
break;
-
t *= DELTA;
-
}
-
}
-
-
int main()
-
{
-
freopen("TSP.data", "r", stdin);
-
int n;
-
Input(p, n);
-
GetDist(p, n);
-
Init(n);
-
SA(n);
-
Print(path, n);
-
printf("Total test times is : %d\n", nCase);
-
return 0;
-
}
數據:TSP.data
-
27
-
41 94
-
37 84
-
53 67
-
25 62
-
7 64
-
2 99
-
68 58
-
71 44
-
54 62
-
83 69
-
64 60
-
18 54
-
22 60
-
83 46
-
91 38
-
25 38
-
24 42
-
58 69
-
71 71
-
74 78
-
87 76
-
18 40
-
13 40
-
82 7
-
62 32
-
58 35
-
45 21