模擬退火
Simulated annealing (SA)
Author: ianwen
Intro
what's simluated annealing
退火
金屬受熱後,活性、延展性增加

退火
金屬受熱後,活性、延展性增加
隨著時間溫度降低,活性降低
模擬退火就是模擬退火
Hiking
以爬山為例
如何找到最高峰?
如何找到最高峰?
一種想法是,只往上走
如何找到最高峰?
一種想法是,只往上走
如何找到最高峰?
一種想法是,只往上走
如何找到最高峰?
但這樣很容易只走到 “局部最優解”
如何用模擬退火找到最高峰?
一開始溫度高,活性較大,允許非最優解的走法
隨著時間溫度降低,逐漸回到僅接受最優解
如何用模擬退火找到最高峰?
初始溫度大,活性大
如何用模擬退火找到最高峰?
活性大
如何用模擬退火找到最高峰?
溫度隨時間衰變,活性變小
如何用模擬退火找到最高峰?
活性變小
如何用模擬退火找到最高峰?
活性變小
如何用模擬退火找到最高峰?
成功避免陷入局部最佳解
如何用模擬退火找到最高峰?
成功避免陷入局部最佳解
如何用模擬退火找到最高峰?
但我們要如何知道"初始溫度"
以及"溫度衰減速度"
不知道,自己調適!
如何用模擬退火找到最高峰?
所以模擬退火,並不能保證
一定找到全局最佳解
但他可以,增加找到全局最佳解的機率

Implementation
實作 SA
一樣找高峰,給你一個連續函數的山
隨機選一個起始點
隨機選一個走法
根據當前溫度選擇是否接受該走法
接受
不接受
修改當前位置
一樣找高峰,給你一個連續函數的山
根據當前溫度選擇是否接受該走法
P 為接受該方案的機率,E 為高度,T 為當前溫度
Visualization - Python
import numpy as np
import matplotlib.pyplot as plt
# 一個長得像 y = sin(2x) + cos(x) 的山
def f(x):
return np.sin(2 * x) + np.cos(x)
T = 1.0 # 溫度
T_min = 0.1 # 限制最低溫
alpha = 0.95 # 溫度衰減率
x = np.random.uniform(-2*np.pi, 2*np.pi) # X
best_x = x
history = [(x, f(x))] # 存每一步高度
# 模擬退火
while T > T_min: # 溫度還沒達到最低溫
x_new = x + np.random.uniform(-0.5, 0.5) # 選一個隨機方案
delta = f(x_new) - f(x) # 隨機走法高度改變多少
if delta > 0 or np.random.rand() < np.exp(delta / T): # 檢查是否接受這個走法
x = x_new # 接受,改變當前位置
if f(x) > f(best_x):
best_x = x # 維護最大值
history.append((x, f(x)))
T *= alpha # 溫度衰減
# 🎯 繪製時間對高度
steps = np.arange(len(history))
heights = [val[1] for val in history]
print(f(best_x))
plt.figure(figsize=(8, 4))
plt.plot(steps, heights, label='Current f(x)')
plt.xlabel("Iteration")
plt.ylabel("Function Value (Height)")
plt.title("Simulated Annealing: f(x) Over Time")
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
Problem
SA 題目
如圖,有 n 個重物,每個重物系在一條足夠長的繩子上。
每條繩子自上而下穿過桌面上的洞,然後系在一起。圖中的 x 位置就是公共的繩結。假設繩子是完全彈性的(即不會造成能量損失),桌子足夠高(重物不會垂到地上),且忽略所有的摩擦,求繩結 x 最終平衡於何處。
注意:桌面上的洞都比繩結 x 小得多,所以即使某個重物特別重,繩結 x 也不可能穿過桌面上的洞掉下來,最多是卡在某個洞口處。

Thoughts
考量 n 個物體,對中心的力矩T是 距離D * 物重W
T可以看為一種能量
而能量越低,越穩定,所以我們可以將此題轉換為
找 n 物體造成的能量總和最低時的點,即
AC Code - C++
#include <bits/stdc++.h>
#define DOWN 0.996 // 降溫速度
using namespace std;
struct obj {
int x, y, w;
} object[2005];// 開一個物體陣列
int n;
double ansX, ansY, ansW; // 維護答案
// 加總每個物體給中心的能量,最低能即平衡點
double energy(double x, double y) {
double r = 0, dx, dy;
for (int i = 1; i <= n; ++i) {
dx = x - object[i].x;
dy = y - object[i].y;
r += sqrt(dx * dx + dy * dy) * object[i].w;
}
return r;
}
// SA
void sa() {
double t = 3000; // 初溫
while (t > 1e-15) { // 最低溫 1e-15
double ex = ansX + (rand() * 2 - RAND_MAX) * t; // 隨機
double ey = ansY + (rand() * 2 - RAND_MAX) * t;
double ew = energy(ex, ey);
double de = ew - ansW;
// 必然接受更優解
if (de < 0) {
ansX = ex;
ansY = ey;
ansW = ew;
}
// 一定機率接受非更優解
else if (exp(-de / t) * RAND_MAX > rand()) {
ansX = ex;
ansY = ey;
}
t *= DOWN; // 降溫
}
}
// 多跑幾次,增加機率
void solve() {
sa();
sa();
sa();
sa();
}
int main() {
// 物體數
cin >> n;
// 物XY 物體重
for (int i = 1; i <= n; ++i) {
cin >> object[i].x >> object[i].y >> object[i].w;
ansX += object[i].x;
ansY += object[i].y;
}
// 一開始選平均值
ansX /= n;
ansY /= n;
ansW = energy(ansX, ansY); // 初始能量
// SA
solve();
// 輸出答案
cout << fixed << setprecision(3) << ansX << " " << ansY << endl;
}
Simulated annealing
By wen Ian
Simulated annealing
- 32