OSDN Git Service

Update hill-climbing.md
authorREM_001 <47171629+haaaaaaaaaruka@users.noreply.github.com>
Sat, 27 Jul 2019 08:22:01 +0000 (16:22 +0800)
committerGitHub <noreply@github.com>
Sat, 27 Jul 2019 08:22:01 +0000 (16:22 +0800)
docs/misc/hill-climbing.md

index 4f8ea85..8b69a72 100644 (file)
 
 爬山算法是一种局部择优的方法,采用启发式方法,是对深度优先搜索的一种改进,它利用反馈信息帮助生成解的决策。
 
-* * *
+直白地讲,就是当目前无法直接到达最优解,但是可以判断两个解哪个更优的时候,根据一些反馈信息生成一个新的可能解。
+
+因此,爬山算法每次在当前找到的最优方案 $x$ 附近寻找一个新方案)。如果这个新的解 $x'$ 更优,那么转移到 $x'$ 否则不变。
 
-## 实现
+这种算法对于单峰函数显然可行。
 
-爬山算法每次在当前找到的最优方案 $x$ 附近寻找一个新方案(一般随机差值)。如果这个新的解 $x'$ 更优,那么转移到 $x'$ 否则不变。
+> Q:你都知道是单峰函数了为什么不三分呢
+> A:在多年的OI生活中,我意识到了,人类是有极限的,无论多么工于心计,绞尽脑汁,状态总是表示不出来的,出题人的想法总是猜不透的,边界总是写不对的——所以——我不三分了JOJO!
 
¿\99ç§\8dç®\97æ³\95对äº\8eå\8d\95å³°å\87½æ\95°æ\98¾ç\84¶å\8f¯è¡\8cï¼\88ä½ é\83½ç\9f¥é\81\93æ\98¯å\8d\95å³°å\87½æ\95°äº\86为ä»\80ä¹\88ä¸\8dä¸\89å\88\86å\91¢ï¼\89
®¤ç\9c\9få\9c°è¯´ï¼\8cç\88¬å±±ç®\97æ³\95ç\9a\84ä¼\98å\8a¿å\9c¨äº\8eå½\93正解ç\9a\84å\86\99æ³\95你并ä¸\8däº\86解ï¼\88常è§\81äº\8eæ¯\92ç\98¤è®¡ç®\97å\87 ä½\95å\92\8cæ¯\92ç\98¤æ\95°å­¦é¢\98ï¼\89ï¼\8cæ\88\96è\80\85æ\9c¬èº«ç\8a¶æ\80\81维度å¾\88å¤\9aï¼\8cæ\97 æ³\95容æ\98\93å\9c°å\86\99å\88\86æ²»ï¼\88ä¾\8b2å°±å\8f¯ä»¥ç\94¨äº\8cå\88\86å®\8cæ\88\90å\90\88æ³\95正解ï¼\89æ\97¶ï¼\8cå\8f¯ä»¥é\80\9aè¿\87é\9d\9e常æ\9a´å\8a\9bç\9a\84计ç®\97å¾\97å\88°æ\9c\80ä¼\98解
 
 但是对于多数需要求解的函数中,爬山算法很容易进入一个局部最优解,如下图(最优解为 $\color{green}{\Uparrow}$ ,而爬山算法可能找到的最优解为 $\color{red}{\Downarrow}$ )。
 
 ![](./images/hill-climbing.png)
 
+
 * * *
 
-## 代码
+## 具体实现
+
+爬山算法一般会引入类似模拟退火的温度解决。类比地说,爬山算法就是一只兔子喝醉了在山上跳,它每次都会朝着它以为更高的地方(这往往只是个不准确的趋势)跳,显然它有可能一把跳到山顶,也可能跳过头翻到对面去了,不过没有关系,兔子翻过去之后还会跳回来。显然这个过程很没有用,兔子永远都找不到出路,所以在这个过程中兔子冷静下来并每次跳的时候更加谨慎,少跳一点,以到达合适的最优点。
+
+兔子清醒的过程就是降温过程,在爬山的时候会不断减小之。
+
+关于降温:一般而言降温参数设定在0.985-0.999,是略小于1的常数。
+
+# 例1 球形空间产生器
+
+这里以一道(其实正解是高斯消元)的适合爬山的好题[LuoguP4035 [JSOI2008]球形空间产生器](https://www.luogu.org/problem/P4035)为例
+
+题意:给出N维空间中的N个点,已知他们在一个N维球上,求出球心。n<=10,坐标绝对值不超过10000.
+
+很明显的单峰函数,可以使用爬山结局。本题算法流程:
+
+1、初始化球心为各个维度平均值,减少枚举量
+
+2、对于当前的球心,求出每个已知点到这个球心欧氏距离的平均值。
+
+3、遍历所有已知点。记录一个改变值cans(分开每一维度记录)对于每一个点的欧氏距离,如果大于平均值,就把改变值加上差值,否则减去。实际上并不用判断这个大小问题,只要不考虑绝对值,直接用坐标计算即可。这个过程可以形象地转化成一个新的球心,在空间里推来推去,碰到太远的点就往点的方向拉一点,碰到太近的点就往点的反方向推一点。
+
+4、将我们记录的cans乘上温度,更新球心,回到步骤2
+
+5、在温度非常小的时候结束。
+
+
+因此,我们在更新球心的时候,不能直接加上改变值,而是要乘上温度。
+
+并不是每一道爬山题都可以具体地用温度解决,这只是一个例子。
+
+
+代码如下:
+```
+#include <bits/stdc++.h>
+using namespace std;
+double ans[10001],cans[100001],dis[10001],tot,f[1001][1001],seed=718;
+int n;
+double check()
+{
+       tot=0;
+       for(int i=1;i<=n+1;i++)
+       {
+               dis[i]=0;cans[i]=0;
+               for(int j=1;j<=n;j++)
+                       dis[i]+=(f[i][j]-ans[j])*(f[i][j]-ans[j]);
+               dis[i]=sqrt(dis[i]);//欧氏距离
+               tot+=dis[i];
+       }
+       tot/=(n+1);//平均
+       for(int i=1;i<=n+1;i++)
+       for(int j=1;j<=n;j++)
+       cans[j]+=(dis[i]-tot)*(f[i][j]-ans[j])/tot;//对于每个维度把修改值更新掉,欧氏距离差*差值贡献
+}
+int main()
+{
+       srand(seed);
+       cin>>n;
+       for(int i=1;i<=n+1;i++)
+               for(int j=1;j<=n;j++)
+               {
+                       cin>>f[i][j];
+                       ans[j]+=f[i][j];
+               }
+       for(int i=1;i<=n;i++)ans[i]/=(n+1);//初始化
+       for(double t=10001;t>=0.0001;t*=0.9999)//不断降温
+       {
+               check();
+               for(int i=1;i<=n;i++)
+                       ans[i]+=cans[i]*t;//修改
+       }
+       for(int i=1;i<=n;i++)printf("%.3f ",ans[i]);
+       
+}
+```
+
+* * *
+
+# 例2 吊打XXX
 
 此处代码以[「BZOJ 3680」吊打 XXX](https://www.lydsy.com/JudgeOnline/problem.php?id=3680)(求 $n$ 个点的带权类费马点)为例。
 
+框架类似,用了点物理知识。
+
 ```cpp
 #include <cmath>
 #include <cstdio>
@@ -58,6 +142,12 @@ int main() {
 
 * * *
 
+## 优化
+
+很容易想到的是,为了尽可能获取优秀的答案,我们可以多次爬山。方法有修改初始状态/修改降温参数/修改初始温度等,然后开一个全局最优解记录答案。每次爬山结束之后,更新全局最优解。
+
+这样处理可能会存在的问题是超时,在正式考试时请手造大数据测试调参。
+
 ## 劣势
 
 其实爬山算法的劣势上文已经提及:它容易陷入一个局部最优解。当目标函数不是单峰函数时,这个劣势是致命的。因此我们要引进[ **模拟退火** ](/misc/simulated-annealing/)。