• 「学习笔记」爬山算法与模拟退火


    爬山算法(Hill Climbing )

    爬山算法(Hill Climbing )是一种局部择优的方法,采用启发式方法,是对深度优先搜索的一种改进,它利用反馈信息帮助生成解的决策。 属于人工智能算法的一种。

    有一些问题,是找全局最大值的。题目所述出自变量与值之间的关系是函数,而这函数图像往往像山的形状,可能有许多局部最大、局部最小。于是“爬山”、“高地”、“山脊”等词形象地表示了算法的作用与函数图像的某些部分.

    解决这类问题一般有两种方法:爬山算法(Hill Climbing )与模拟退火(Simulated Annealing)。常用的为模拟退火,它往往可以得到最优解。不过这里先介绍爬山算法。

    爬山算法是一种简单的贪心搜索算法,该算法每次从当前解的临近解空间中选择一个最优解作为当前解,直到达到一个局部最优解。

    爬山算法的优点为:避免遍历,通过启发选择部分节点,从而达到提高效率的目的。但是有缺点:只能找到局部最优,一般找不到全局最优解;搜索一旦到达高地,就无法确定搜索最佳方向,会产生随机走动,使得搜索效率降低;搜索可能会在山脊的两面来回震荡,前进步伐很小。

    下图反应了爬山算法相对于模拟退火的目光短浅之处(模拟退火也是随机算法,不一定可以找到最优解,但大部分情况可以找到)

    模拟退火(Simulated Annealing)

    退火是物理热力学里的概念。退火是将金属加热到一定温度,保持足够时间,然后以适宜速度冷却的一种金属热处理工艺。我们把这个过程模拟一下,就叫模拟退火(Simulated Annealing)。

    爬山算法就是贪心法。而模拟退火是一种随机搜索算法。它的描述是这样的:

    若移动后得到更优解,则总是接受该移动

    若移动后的解比当前解要差,则以一定的概率接受移动,而且这个概率随着时间推移逐渐降低。

    根据热力学的原理,降温的概率(P)表示为:

    [p = e^{-dfrac{Delta E}{kT}} ]

    (Delta E):新状态与当前状态的能量差

    (k):常数

    (T):时间

    (Delta E<0)时,一定接受。(Delta E>0)时就不一定了,并且 (x) 越来越小的时,函数值越来越趋近于 (0),这也就是说随着 (T) 的增加,概率越来越小,趋向稳定。

    我们要维护 (T),初始温度设置为 (T_0)(较大),降温系数为 (d),最终温度为 (T_k)

    首先令 (T = T_0);每次让 (T=dT)(T<T_k) 时降温结束,此时为最优解。

    调参时一般会调 (d)(0<d<1))。(d) 过大降温慢,得到最优解的可能性大(容易 TLE);(d) 过小降温快,但得到最优解的可能性小(容易 WA);

    那模拟退火具体怎么使用代码实现?

    每次循环内有 4 步:

    根据当前解找到下一个解

    计算下一个解的 "能量"

    决定是否要接受这个新解

    降温

    找下一个解时一般生成随机区间 ([−1,1]) 的随机数再乘上 (T) 作为差值. 得到一个 ([−T,T]) 的随机值作为差值,也就是说 (T) 越低,它随机到下一步的范围就越小。

    伪代码:

    当前温度 = 初始温度;

    while 当前温度 > 终温:

    根据 T 随机生成下一步;

    计算出下一步的能量;

    计算能量差;

    if 接受:更新答案;

    当前温度 乘以 降温系数;


    推荐几道模拟退火的例题:

    HDU 2899 比较好的入门题

    HDU 3932 二维相关

    NOIP2017 宝藏 骗分甚至AC

    POJ 3311 TSP问题

    例题讲解

    HDU 2899 (求函数最值)

    #include <iostream>
    #include <cstdlib>
    #include <cstdio>
    #include <cmath>
    #include <ctime>
    using namespace std;
    
    double y;
    
    double F(double x) {
        return 6 * pow(x, 7) + 8 * pow(x, 6) + 7 * pow(x, 3)
        + 5 * pow(x, 2) - y * x;
    }
    
    double Rand01() {
        return rand() / (double)RAND_MAX;
    }
    
    double Rand(double T) {
        int f = rand() & 1;
        return (f ? -1 : 1) * T * Rand01();
    }
    
    int main() {
        srand(time(NULL));
        int Case;
        scanf("%d", &Case);
        while(Case --) {
            scanf("%lf", &y);
            double T = 100, T_end = 1e-6, d = 0.85, ans = F(0);
            double x_now = 0, x_next;
            while(T > T_end) {
                for(int i = 0; i < 2; i ++) {
                    x_next = x_now + Rand(T);
                    if(x_next < 0 || x_next > 100) continue;
                    double f_next = F(x_next), f_now = F(x_now);
                    double delta = f_next - f_now;
                    if(delta < 0 || Rand01() < exp(- delta / T)) {
                        x_now = x_next;
                        ans = min(ans, f_next);
                    }
                }
                T *= d;
            }
            printf("%.4f
    ", ans);
        }
        return 0;
    }
    
    

    HDU 3932 (矩形中找距离和最小点)

    #include <iostream>
    #include <cstdlib>
    #include <cstdio>
    #include <cmath>
    using namespace std;
    
    const double PI = acos(-1.0);
    
    int X, Y, N;
    int px[1010], py[1010];
    
    double pw(double x) {
        return x * x;
    }
    
    double calc(double x, double y) {
        double dis = 0;
        for(int i = 1; i <= N; i ++)
            dis = max(dis, sqrt(pw(x - px[i]) + pw(y - py[i])));
        return dis;
    }
    
    double Rand01() {
        return rand() / (double) RAND_MAX;
    }
    
    double Rand() {
        int f = rand() & 1;
        return (f ? -1 : 1) * Rand01();
    }
    
    int main() {
        srand(12834917);
        while(~ scanf("%d%d%d", &X, &Y, &N)) {
            for(int i = 1; i <= N; i ++)
                scanf("%d%d", &px[i], &py[i]);
            double x = 0, y = 0;
            double ans = calc(x, y), ansx = x, ansy = y;
            double T = sqrt(X * X + Y * Y), T_end = 1e-4, d = 0.99;
            while(T > T_end) {
                for(int i = 0; i < 100; i ++) {
                    double theta = Rand01() * 2 * PI;
                    double nx = x + T * cos(theta);
                    double ny = y + T * sin(theta);
                    if(nx < 0 || ny < 0 || nx > X || ny > Y) continue;
                    double next_ans = calc(nx, ny);
                    double now_ans = calc(x, y);
                    double delta = next_ans - now_ans;
                    if(delta < 0 || Rand01() < exp(- delta / T)) {
                        x = nx, y = ny;
                        if(next_ans < ans) {
                            ans = next_ans;
                            ansx = x, ansy = y;
                        }
                    }
                }
                T *= d;
            }
            printf("(%.1f,%.1f).
    %.1f
    ", ansx, ansy, ans);
        }
        return 0;
    }
    
    

    NOIP2017 宝藏

    PS:调参十分麻烦。听说有人考试的时候骗了95分(雾

    #include <iostream>
    #include <cstring>
    #include <cstdlib>
    #include <cstdio>
    #include <cmath>
    #include <ctime>
    using namespace std;
    
    const int N = 12;
    const int INF = 1 << 29;
    
    int n, m, s;
    int G[N][N], K[N], a[N], b[N];
    
    int calc(int *a) { //O(n^2)
    	int ans = 0;
    	K[ a[0] ] = 1;
    	for(int i = 1; i < n; i ++) {
    		int j = a[i], mc = INF, mk;
    		for(int k = 0; k < i; k ++) {
    			if(G[a[k]][j] < INF && mc > K[a[k]] * G[a[k]][j])
    				mc = (mk = K[a[k]]) * G[a[k]][j];
    		}
    		if(mc == INF) return INF;
    		K[j] = mk + 1;
    		ans += mc;
    	}
    	return ans;
    }
    
    double Rand01() {
    	return rand() / (double) RAND_MAX;
    }
    
    void Change(int times) {
    	for(int i = 0; i < n; i ++) b[i] = a[i];
    	for(int i = 1; i <= times; i ++)
    		swap(b[rand() % n], b[rand() % n]);
    }
    
    int main() {
    	srand(time(NULL));
    	scanf("%d%d", &n, &m);
    	memset(G, 127, sizeof G);
    	for(int i = 1, u, v, w; i <= m; i ++) {
    		scanf("%d%d%d", &u, &v, &w);
    		-- u, -- v;
    		G[u][v] = G[v][u] = min(G[u][v], w);
    	}
    	for(int i = 0; i < n; i ++) a[i] = i;
    	int ans = calc(a);
    	double T = 6e6, T_end = 100, d = 0.98;
    	while(T > T_end) {
    		for(int i = 0; i < 10; i ++) {
    			Change(T / 100);
    			int now_ans = calc(a), next_ans = calc(b);
    			int delta = next_ans - now_ans;
    			if(delta < 0 || Rand01() < exp(- delta / T)) {
    				for(int i = 0; i < n; i ++) a[i] = b[i];
    				if(next_ans < ans) ans = next_ans;
    			}
    		}
    		T *= d;
    	}
    	printf("%d
    ", ans);
    	return 0;
    }
    
    

    POJ 3311 TSP

    #include <iostream>
    #include <cstdlib> 
    #include <cstdio>
    #include <cmath>
    using namespace std;
    
    int n, G[20][20], a[20], b[20];
    
    int calc(int *a) {
    	int now = 1, ans = 0;
    	for(int i = 2; i <= n; now = a[i ++])
    		ans += G[now][ a[i] ];
    	return ans + G[now][1];
    }
    
    int Rand() {
    	return rand() % (n - 1) + 2;
    }
    
    double Rand01() {
    	return rand() / (double) RAND_MAX;
    }
    
    void Change(int times) {
    	for(int i = 2; i <= n; i ++) b[i] = a[i];
    	for(int i = 1; i <= times; i ++)
    		swap(b[Rand()], b[Rand()]);
    }
    
    int main() {
    	srand(91874899);
    	while(scanf("%d", &n), n ++ != 0) {
    		for(int i = 1; i <= n; a[i] = i, i ++)
    			for(int j = 1; j <= n; j ++)
    				scanf("%d", &G[i][j]);
    		for(int k = 1; k <= n; ++ k)  
                for(int i = 1; i <= n; ++ i)  
                    for(int j = 1; j <= n; ++ j)  
                        if(G[i][k] + G[k][j] < G[i][j])  
                            G[i][j] = G[i][k] + G[k][j];  
    		int ans = calc(a);
    		double T = n * 10, T_end = 1e-4, d = 0.98;
    		while(T > T_end) {
    			for(int i = 0; i < 100; i ++) {
    				if((int)T == 0) break;
    				Change(T);
    				int now_ans = calc(a);
    				int next_ans = calc(b);
    				int delta = next_ans - now_ans;
    				if(delta < 0 || Rand01() < exp(-delta / T)) {
    					for(int i = 2; i <= n; i ++) a[i] = b[i];
    					if(next_ans < ans) ans = next_ans;
    				}
    			}
    			T *= d;
    		}
    		printf("%d
    ", ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    用python实现批量替换.doc文件文件内容
    记我是如何通过Security+考试的
    去哪儿笔试羊生羊问题
    CSS作用域问题
    常用的几种清除浮动的方式
    ECE转专业找工作经历-从零电面到facebook offer
    面经-Bloomberg
    题解-Linked List Cycle II
    自我介绍
    解题技巧-发现子结构
  • 原文地址:https://www.cnblogs.com/hongzy/p/9318897.html
Copyright © 2020-2023  润新知