爬山算法与模拟退火

爬山算法(Hill Climbing )

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

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

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

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

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

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

模拟退火(Simulated Annealing)

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

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

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

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

扫描二维码关注公众号,回复: 2186135 查看本文章

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

Δ 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\n", 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).\n%.1f\n", 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\n", 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\n", ans);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/cute-hzy/p/9318897.html