這是世界上最玄學的算法——模擬退火學習筆記


\[\Huge\text{模擬退火學習筆記} \]


模擬退火是什么?

模擬退火來自冶金學的專有名詞退火。退火是將材料加熱后再經特定速率冷卻,目的是增大晶粒的體積,並且減少晶格中的缺陷。材料中的原子原來會停留在使內能有局部最小值的位置,加熱使能量變大,原子會離開原來位置,而隨機在其他位置中移動。退火冷卻時速度較慢,使得原子有較多可能可以找到內能比原先更低的位置。

模擬退火的原理也和金屬退火的原理近似:我們將熱力學的理論套用到統計學上,將搜尋空間內每一點想像成空氣內的分子;分子的能量,就是它本身的動能;而搜尋空間內的每一點,也像空氣分子一樣帶有“能量”,以表示該點對命題的合適程度。算法先以搜尋空間內一個任意點作起始:每一步先選擇一個“鄰居”,然后再計算從現有位置到達“鄰居”的概率。

可以證明,模擬退火算法所得解依概率收斂到全局最優解。

演算步驟

  1. 初始化

生成一個可行的解作為當前解輸入迭代過程,並定義一個足夠大的數值作為初始溫度

  1. 迭代

迭代過程是模擬退火算法的核心步驟,分為新解的產生和接受新解兩部分

  1. 由一個產生函數從當前解產生一個位於解空間的新解;為便於后續的計算和接受,減少算法耗時,通常選擇由當前新解經過簡單地變換即可產生新解的方法,如對構成新解的全部或部分元素進行置換、互換等,注意到產生新解的變換方法決定了當前新解的鄰域結構,因而對冷卻進度表的選取有一定的影響。
  2. 計算與新解所對應的目標函數差。因為目標函數差僅由變換部分產生,所以目標函數差的計算最好按增量計算。事實表明,對大多數應用而言,這是計算目標函數差的最快方法。
  3. 判斷新解是否被接受,判斷的依據是一個接受准則,最常用的接受准則是Metropolis准則:若Δt′<0則接受S′作為新的當前解S,否則以概率exp(-Δt′/T)接受S′作為新的當前解S。
    4.當新解被確定接受時,用新解代替當前解,這只需將當前解中對應於產生新解時的變換部分予以實現,同時修正目標函數值即可。此時,當前解實現了一次迭代。可在此基礎上開始下一輪試驗。而當新解被判定為舍棄時,則在原當前解的基礎上繼續下一輪試驗。

注:模擬退火算法與初始值無關,算法求得的解與初始解狀態S(是算法迭代的起點)無關;模擬退火算法具有漸近收斂性,已在理論上被證明是一種以概率1收斂於全局最優解的全局優化算法;模擬退火算法具有並行性。

  1. 停止准則

迭代過程的停止准則:溫度T降至某最低值時,完成給定數量迭代中無法接受新解,停止迭代,接受當前尋找的最優解為最終解。

  1. 退火方案

在某個溫度狀態T下,當一定數量的迭代操作完成后,降低溫度T,在新的溫度狀態下執行下一個批次的迭代操作。

  • 上述內容摘自wiki百科

整個流程圖如下:

看不懂上面說的,怎么辦?

沒事,第一次看的時候我也看不懂,因此下面我用我的理解來講述這個歐皇(划去)算法

和之前一樣,我們通過一道題目來理解

\[[JSOI2004]平衡點 \]

\(n\)個重物,每個重物系在一條足夠長的繩子上。每條繩子自上而下穿過桌面上的洞,然后系在一起。X處就是公共的繩結。假設繩子是完全彈性的(不會造成能量損失),桌子足夠高(因而重物不會垂到地上),且忽略所有的摩擦。問繩結X最終平衡於何處。

注意:桌面上的洞都比繩結X小得多,所以即使某個重物特別重,繩結X也不可能穿過桌面上的洞掉下來,最多是卡在某個洞口處。

先講一下正解的思路:

在平面上確定一個原點,將所有的力在這個平面上正交分解,從而求得所有力的合力的方向。

然后使用一個半平面切割初始的凸包,形成一個新的凸包,這樣操作后凸包的面積會變小,進行多次二分,直到凸包的面積達到精度以內(即小到可以看做一個點),這時任選凸包的一個頂點就是答案。

若二分次數為\(k\),算法的時間復雜度為\(O(kn)\)

至此正解結束

但這個正解很low,顯示不出我們OIer真正的強大(暴力)之處,因此,我們選擇模擬退火來AC這道題!

代碼實現

很明顯,我們先開一個結構體儲存物體信息

struct Node
{
	int x,y,w;
}object[2005];
//儲存物體的坐標和重量

然后我們需要一個函數來計算新的系統的能量,因為根據物理學知識,一個系統的能量之和越小則越穩定

double energy(double x,double y)//計算系統新的能量
{
	double r=0,dx,dy;
	for(register 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;
}//根據物理學知識,系統能量之和越小則越穩定

接着就是多次迭代退火,逼近最優值

void sa()//simulated annealing,模擬退火
{
	double t=3000;//將初始溫度設為3000°C
	while(t>eps)//控制精度
	{
		double newx=ansx+(rand()*2-RAND_MAX)*t;
		double newy=ansy+(rand()*2-RAND_MAX)*t;
		double neww=energy(newx,newy);
		double delta=neww-answ;
		if(delta<0)//新的系統能量更小(更穩定)
		{
			ansx=newx;
			ansy=newy;
			answ=neww;
		}
		else if(exp(-delta/t)*RAND_MAX>rand())//概率接受
		{
			ansx=newx;
			ansy=newy;
		}
		t*=down;//操作完畢后降溫
	}
}

然后在主函數中多次退火就行了。

交代碼前需要注意最重要的一步——洗臉!(笑)

模擬退火是玄學算法,因此需要多交幾次才能過

至於某臉黑的tqr……

附上AC代碼(調了很久才調出來的降溫參數)

#include<bits/stdc++.h>
#define eps 1e-15
using namespace std;
const double down=0.994;
//降溫系數,緩慢降溫,關於降溫參數的選取……這是個玄學問題

int n;
double ansx,ansy,answ;
//最終的答案

struct Node
{
    int x,y,w;
}object[2005];
//儲存物體的坐標和重量

double energy(double x,double y)//計算系統新的能量
{
    double r=0,dx,dy;
    for(register 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;
}//根據物理學知識,系統能量之和越小則越穩定

void sa()//simulated annealing,模擬退火
{
    double t=3000;//將初始溫度設為3000°C
    while(t>eps)//控制精度
    {
        double newx=ansx+(rand()*2-RAND_MAX)*t;
        double newy=ansy+(rand()*2-RAND_MAX)*t;
        double neww=energy(newx,newy);
        double delta=neww-answ;
        if(delta<0)//新的系統能量更小(更穩定)
        {
            ansx=newx;
            ansy=newy;
            answ=neww;
        }
        else if(exp(-delta/t)*RAND_MAX>rand())//概率接受
        {
            ansx=newx;
            ansy=newy;
        }
        t*=down;//操作完畢后降溫
    }
}

template<class T>inline void read(T &res)
{
    T flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
    while((c=getchar())>='0'&&c<='9')res=(res<<1)+(res<<3)+c-'0';res*=flag;
}

int main()
{
    read(n);
    for(register int i=1;i<=n;++i)
    {
        read(object[i].x);ansx+=object[i].x;
        read(object[i].y);ansy+=object[i].y;
        read(object[i].w);
    }
    ansx/=n;ansy/=n;
    answ=energy(ansx,ansy);
    sa();sa();sa();sa();sa();sa();
    printf("%.3lf %.3lf",ansx,ansy);
    return 0;
}

UPD:我發現0.994的參數,退八次火AC率很高,但不知道為什么,dalao求解qwq

https://home.cnblogs.com/u/tqr06/

https://www.cnblogs.com/tqr06/p/10400144.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM