模擬退火算法詳解


別着急,干貨在最后面!!!

(本文用c++實現,可以在評論區討論,后面還有情況的話還會更新,有問題歡迎指正哦~)

可以在右上角看目錄,左下角點歌哦(不行的話刷新一下就好了~)

本文章也介紹了模擬退火的使用情景,以免誤入歧途(本蒟蒻就是)。

很多人都學過貪心,但是貪心在一些情況並不適用,比如:

已知我們從黃色出發,找最小值。

貪心策略當然是一直往函數大小減小的地方偏移——但是,萬一不是單谷呢?我們會陷入如圖的藍色中無法自拔。

肯能你會想到:隨機找一個點出發,然后貪心找最小值?多隨機幾遍,然后求全局最小值?

你會發現復雜度暴增!!!!!!!!——所以如何處理這種問題呢?

模擬退火:啊,對對對~

是的!模擬退火就是一種類似於隨機化貪心的一個算法,在OI界也小有名氣(冥器)!(如題[NOIP2021] 方差

原理圖:

如圖:在物理應用中分子排布可能是紊亂的,如果我們將它升溫然后緩慢降溫,就可以生成完美的晶形!

而對於我們求解的:

 

怎么形象描述它呢?

一個有自己一定卡路里的人爬山,想翻山找遠方的草葯給自己心愛的妻子,但是他並不知道山的那頭是什么。所以他會在卡路里多的時候盡量去遠方探險,但是每次會花費卡路里以至於他后面不能翻過太高的山丘,而且他的背包蠻大的,裝填着無數愛的草葯而芳香四溢。

所以我們立刻(啊,對對對~)能設定模擬退火的參數:

1.初始溫度 T (1000-7000)

2.末尾溫度 P(1e-6~1e-15)

3.降溫系數 K (0.91~0.9975)

4.狀態空間(被降溫物體) S

5.當前能量 E(new)

6.全局能量 E(old)

一:Metropolis准則

以概率接受新狀態:

 

 

 

這就是物理(化學)方面類似的推論——一定概率的更新。

什么意思呢?

我們已知:當前能量 E ( new ) , 全局能量 E ( old ),那么我們的目標是什么,不就是減少目前的能量嗎?

所以:當當前能量少於全局能量(即更新前的能量),那么我們有概率為 1 的更新概率;

      當當前能量大於全局能量(即更新前的能量),那么我們有概率為 exp( - (E( new )-E( old ))/T) 的更新概率 ( T為當前溫度)

[exp(x)函數:e的x次方的函數  如 exp(1)表示e的1次方=e=2.718281828…  exp(0)表示e的0次方=1  exp(2)表示e的平方=7.3890561…  e是一個常數,等於2.718281828…];

注意:有時候也不一定以以上方式更新,這只是比較妥的做法,概率方面是可以自己定的,但是一定以當前能量與全局能量的關系來設定的。(除非直接暴力的隨機算法)

二:生成新溫度

那么怎么生成新的當前溫度呢?,以生成小數為例:

當前將更新溫度=全局溫度+(rand()*2-RAND_MAX)*t;
if(不在狀態空間內){
    當前將更新溫度=fmod(當前將更新溫度,狀態空間大小)
}

  即:在當前狀態的鄰域結構內以一定概率方式(均勻分布、正態分布、指數分布等)產生。

三:溫度更新函數

若固定每一溫度,算法均計算至平穩分布,然后下降溫度,則稱為時齊算法;

若無需各溫度下算法均達到平穩分布,但溫度需按一定速率下降,則稱為非時齊算法。

本人用的:

T*=K;

四:外循環終止准則 

本人使用的:

(t>1e-15)//可以改大一點

其他常用方法:

    (1)設置終止溫度的閾值。

    (2)設置外循環迭代次數。

    (3)算法搜索到的最優值連續若干步保持不變。

    (4)概率分析方法。

五:實現流程圖:

六:關於其他類似算法的優缺點比較

遺傳算法:其優點是能很好地處理約束,跳出局部最優,最終得到全局最優解。缺點是收斂速度慢,局部搜索能力弱,運行時間長,容易受到參數的影響。

模擬退火:具有局部搜索能力強、運行時間短的優點。缺點是全局搜索能力差,容易受到參數的影響。

爬山算法:顯然爬山算法簡單、效率高,但在處理多約束大規模問題時,往往不能得到較好的解決方案。

七:退火口訣:

初始溫度小心設(1000-3000),又粗又大wa一臉

多次sa更保險,忘了卡時直接T[if((double)clock()/CLOCKS_PER_SEC>=0.993)](七遍模擬退火也行)

退火系數大膽設,不過0.9975會很厄

全局、狀態不一樣,全局必須菊部優

百年騙分一場空,不開srand見祖宗

退火需謹慎,退火不規范,靈封兩行淚

八:使用條件:

我們可以看下這道題:https://www.luogu.com.cn/problem/P6879

第一次做看到最優解我直接退火了,要不是捆綁數據還以為自己正確了。

代碼:

#include <bits/stdc++.h>
using namespace std;
const int N=500;
const double dw=0.9975;
int resx,n,ans,l,T[N];
long long a[N];
bool st[N];
int em(int x){
	memset(st,false,sizeof st);
	int res=0,pd=0;
	pd=min(abs(x-0),abs(x-l));
	for(int i=1;i<=n*2;i++){
		if(abs(a[i]-x)+pd<=T[i]&&!st[i]){
			res++;st[i%(2*n)]=st[(i+n)%(2*n)]=true;
		}
	}
	return res+(rand()-RAND_MAX+1)%2;
}
void sa(){
	double t=1500;
	while(t>1e-15){
		int x=abs(resx+rand()*2-RAND_MAX+1)%l;
		int e=em(x),dt=ans-e;
		if(dt<0){
			ans=e;resx=x;
		}else if(exp((double)(-dt/t))*RAND_MAX<rand()){
			resx=x;
		}
		t*=dw;
	}
}
int main(){
	srand((unsigned)time(0));
	cin>>n>>l;
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		a[i+n]=a[i]+l;
	}
	for(int i=1;i<=n;i++){
		scanf("%d",&T[i]);
		T[i+n]=T[i];
	}
	l*=2;
	ans=em(0);
	sa();sa();sa();sa();sa();sa();sa();
	cout<<ans; 
}

  

發現可以A掉一部分?其實是不對滴——計算當前物品能量的函數寫的並不是正確的。

因為當你到了圓上的一點后,發現你仍然需要下一步決策走向最優解,而並不是直接可以計算出來可以得到的貢獻值。

這不禁讓我反思——退火可以解DP題嗎?

我們可以觀察一下這道題:大的最優解是由更小的最優解轉移過來的,就像一個樹形結構,由子節點向父節點轉移,像這樣的題是不可以用退火的。

但是比如01背包, [NOIP2021]方差 和等類貪心題目是可以做的,因為你可以通過概率水掉局部最優解對全局最優解的不可轉移。

所以,每次做題要看退火的當前能量計算復不復雜,有沒有子問題的限制!

然后是[NOIP2021]方差的實現(玄學萬歲):

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define LL long long
 4 const int N=1e5+10;
 5 const double dw=0.9975;
 6 int a[N],n,c[N];
 7 long long ans;
 8 bool cmp(int a,int b){
 9     return a>b;
10 }
11 LL en(){
12     LL em=0;
13     LL ranss=0;
14     for(int i=2;i<=n;i++){
15         a[i]=a[i-1]+c[i];
16     }
17     for(int i=1;i<=n;i++){
18         em+=(long long)a[i]*a[i];
19     }em=(long long)em*n;
20     for(int i=1;i<=n;i++){
21         ranss=(long long)ranss+a[i];
22     }ranss=(long long)ranss*ranss;
23     return (long long)(em-ranss);
24 }
25 void sa(){
26     double t=1000;
27     while(t>1e-15){
28         if((double)clock()/CLOCKS_PER_SEC>=0.993){
29             cout<<ans;
30             exit(0);
31         }
32         int x=rand()%(n-1)+2,y=rand()%(n-1)+2;
33         while(x==y)x=rand()%(n-1)+2;
34         swap(c[x],c[y]);
35         LL m=en(),dt=ans-m;
36         if(dt>0){
37             ans=m;
38         }else if((double)rand()>=(double)RAND_MAX*(double)exp((double)dt/t)){
39             swap(c[x],c[y]);
40         }
41         t*=dw;
42     }
43 }
44 int main(){
45     srand((unsigned)time(0));
46     cin>>n;
47     for(int i=1;i<=n;i++){
48         scanf("%d",&a[i]);
49         c[i]=a[i]-a[i-1];
50     }
51     sort(c+2,c+n/2+1,cmp);
52     sort(c+n/2+1,c+n+1);
53     ans=en();
54     while(1)sa();
55 }

 附贈導論:騙分


免責聲明!

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



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