爬山法
什么是爬山法?
爬山法是向值增加的方向持续移动到简单循环过程,算法在到达一个“峰顶”时终止,此时相邻状态中没有比该“峰顶”更高的值。爬山法不维护搜索树,当前节点只需要记录当前状态及其目标函数值;爬山法不会前瞻与当前状态不直接相邻的状态的值——“就像健忘的人在大雾中试图登顶珠峰一样”。爬山法从来不会“下山”,只会向值比当前节点好的方向搜索,因而肯定不完备,很容易停留在局部极值上。
怎么实现?
爬山法又称贪婪局部搜索,只是选择相邻状态中最好的一个。尽管贪婪是七宗罪之一,但是贪婪算法往往能够获得很好的效果。当然,爬山法会遇到以下问题:
(1)局部极值
(2)山脊:造成一系列的局部极值
(3)高原:平坦的局部极值区域——解决办法:继续侧向移动
随机爬山法:在上山移动中,随机选择下一步,选择的概率随着上山移动到陡峭程度而变化。
首选爬山法:随机地生成后继节点直到生成一个优于当前节点的后继。
随机重新开始的爬山法:“如果一开始没有成功,那么尝试,继续尝试”算法通过随机生成的初始状态来进行一系列的爬山法搜索,找到目标时停止搜索。该算法以概率1接近于完备:因为算法最终会生成一个目标状态作为初始状态。如果每次爬山搜索成功的概率为p,则需要重新开始搜索的期望次数为 \(1/p\)。
例题P4035 [JSOI2008]球形空间产生器
#include<bits/stdc++.h>
#define rint register int
#define int long long
#define endl '\n'
using namespace std;
const int N = 2e1 + 5;
int n;
double d[N][N],ans[N],dist[N],delta[N];
void calc(){
double avg=0;
for(rint i=0;i<n+1;i++){
dist[i]=delta[i]=0;
for(rint j=0;j<n;j++){
dist[i]+=(d[i][j]-ans[j])*(d[i][j]-ans[j]);
}
dist[i]=sqrt(dist[i]);
avg+=dist[i]/(n+1);
}
for(rint i=0;i<n+1;i++){
for(rint j=0;j<n;j++){
delta[j]+=(dist[i]-avg)*(d[i][j]-ans[j])/avg;
}
}
return ;
}
signed main(){
cin>>n;
for(rint i=0;i<n+1;i++)
for(rint j=0;j<n;j++){
cin>>d[i][j];
ans[j]+=d[i][j]/(n+1);
}
for(double T=1e4;T>1e-6;T*=0.99995){
calc();
for(rint i=0;i<n;i++){
ans[i]+=delta[i]*T;
}
}
for(rint i=0;i<n;i++){
printf("%.3lf ",ans[i]);
}
return 0;
}