[轉]模擬退火算法

模擬退火算法是用來求解最優化問題的算法。比如著名的TSP問題,函數最大值最小值問題等等。接下來將以如下幾個方面來詳細介紹模擬退火算法。

 

Contents

 

   1. 模擬退火算法認識

   2. 模擬退火算法描述

   3. 費馬點問題求解

   4. 最小包含球問題求解

   5. 函數最值問題求解

   6. TSP問題求解

 

 

1. 模擬退火算法認識

 

   爬山算法也是一個用來求解最優化問題的算法,每次都向着當前上升最快的方向往上爬,但是初始化不同可能

   會得到不同的局部最優值,模擬退火算法就可能跳出這種局部最優解的限制。模擬退火算法是模擬熱力學系統

   中的退火過程。在退火過程中是將目標函數作爲能量函數。大致過程如下

 

   初始高溫 => 溫度緩慢下降=> 終止在低溫 (這時能量函數達到最小,目標函數最小)

 

   在熱力學中的退火過程大致是變溫物體緩慢降溫而達到分子之間能量最低的狀態。設熱力學系統S中有有限個且

   離散的n個狀態,狀態的能量爲,在溫度下,經過一段時間達到熱平衡,這時處於狀態的概率爲

 

                   

 

   模擬退火算法也是貪心算法,但是在其過程中引入了隨機因素,以一定的概率接受一個比當前解要差的解,並且

   這個概率隨着時間的推移而逐漸降低。

 

  

2. 模擬退火算法描述

 

   若,即移動後得到更優的解,那麼總是接受改移動。

   若,即移動後得到更差的解,則以一定的概率接受該移動,並且這個概率隨時間推移

   逐漸降低。這個概率表示爲

 

   

 

   由於是退火過程,所以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) //外循環,主要更新參數t,模擬退火過程
{
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

---------------------
作者:acdreamers
來源:CSDN
原文:https://blog.csdn.net/acdreamers/article/details/10019849
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

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