模拟退火(Simulated Annealing)——C语言实现

概述

说退火之前,先来说一说爬山算法(Hill Climbing),一种简单的贪心搜索算法,从临近解中选择一个较优解,循环一直到得到一个最优解,就像是爬山一样,在左右山腰一直跳跃,直到达到山顶为止,但是这个算法有个主要的缺陷就是会陷入局部最优解,就像是爬山爬到一个山峰,但这个不是最高的山峰。那么为了跳出局部最优解而得到整体最优解,就有了这种模拟退火的思想。

模拟退火思想

所谓模拟退火算法(SA算法),思想借鉴了物理中固体的退火过程,并结合组合优化问题之间的联系,得到了这样一种求整体最优解的优化算法。大致分成了三个部分:加温过程,等温过程,冷却过程。
通俗的讲,就是在爬山算法的基础上,设置一个可能性,这个可能性会让它跳出当前的局部最优状况,也就是从一个山头,跳到另一个山头或者是山腰上。这样能够跳出爬山算法的局部最优解的限制,调整温度参数,可以很大程度上得到整体最优解。当然,模拟退火一定程度上是有一定概率接受恶化的解,这个是跳出局部最优的保证。

操作步骤

(1)初始化温度T0,取一个合适的足够大的数,T = T0, 确定每个T的迭代次数L,终止温度T_end。
(2)得到一个初始解,可以随机给出一种路径,或者采用贪心得到一个路径L1。(后者较好,这样优化的次数可以少一些,减少时间耗费)
(3)对当前的路径随机产生一个扰动,(可以用2-opt扰动,参照笔者的另一篇文章)产生一个新的路径L2。
(4)计算新路径的增量D = f(L2) - f(L1),其中的f为一个代价函数,一般来说,对于路径问题就是路径长度。
(5)判断D与0的关系,若f (L2) < f(L1),则更新解为L2,即另L1 = L2;否则一定的概率下,我们仍然更新解L2,这个概率满足exp(-D/T),也就是随机产生一个(0,1)上的随机数n,若exp(-D/T) > n,则更新解为L2,否则不更新解。然后温度T衰减,也就是T = T * q(q为一个(0,1)的函数,表示衰减函数)
(6)重复(3),(4),(5)对温度T,和次数num = 1, 2,3…,L(迭代次数)
(7)满足条件,停止循环,L1则为优化过的整体最优解。条件为:在连续N个迭代L次后新的解L2都没有被接受,或者是温度T达到终止温度T_end

代码实现

int main() {
	game_state_t state;
	memset(&state, 0, sizeof(state));
	init(&state);
//       frome here X1
	int i, j;
	double T = T0;
	double minpath = 0;
	double minpath1 = 0;
	n = state.n;
	m = state.m;
	op.x = state.start_x, op.y = state.start_y;
	AdjGraph G;
	Init(state, &G);
	Floyd(G);
	int num_in = (state.start_x * m + state.start_y);
	for (i = 0; i < n; i++) {
		for (j = 0; j < m; j++) {
			if (state.food[i][j] == 1) {
				n_food[count] = i * m + j;
				count++;
			}
		}
	}
	count1 = count;
	while (count > 0) {
		int nearest;
		double MIN = 9999.9;
		for (i = 0; i < m * n; i++) {
			int u = i / m;
			int v = i % m;
			if (state.food[u][v] == 1 && A[num_in][i] < MIN && num_in != i) {
				MIN = A[num_in][i];
				nearest = i;
			}
		}
		minpath += MIN;
		int num_out = nearest;
		num_in = num_out;
		state.food[num_out / m][num_out % m] = 0;
		pathway[count1 - count] = num_out;
		count--;
	}
	//to here X1这一部分贪心求一个解
	num_in = (state.start_x * m + state.start_y);
	srand((unsigned)time(NULL));
	while (T > T_end) {            //from here X2
		for (int k = 0;k < L; k++) {
			int n1, n2;
			n1 = rand() % count1;
			do {
				n2 = rand() % count1;
			} while (n1 == n2);
			if (n1 > n2) {
				swap(&n1, &n2);
			}
			int left = n1, right = n2;
			while (left < right) {
				swap(&pathway[left], &pathway[right]);
				left++; right--;
			}
			minpath1 = 0;
			for (i = 0; i < count1 - 1; i++) {
				minpath1 += A[pathway[i]][pathway[i + 1]];
			}
			minpath1 += A[num_in][pathway[0]];
			if (minpath1 < minpath) {
				minpath = minpath1;
			}
			else {
				double r = (rand() % 100000)*0.00001;
				if (exp((minpath - minpath1) / T <= r)) {
					left = n1, right = n2;
					while (left < right) {
						swap(&pathway[left], &pathway[right]);
						left++; right--;
					}
				}
			}
		}
		T *= q;
	}
	// to here X2 使用2-opt模拟退火优化贪心求出来的解
	// from here X3
	for (i = 0; i < count1; i++) {
		int num_out = pathway[i];
		BFS(G, num_in, num_out);
		transfor(num_out, &state);
		j = 0;
		while (path_BFS[j] != num_out) {
			printf("%c", p[j]);
			j++;
		}
		num_in = num_out;
	}
	// to here X3 输出路径
	destroy(&state);
	system("PAUSE");
	return 0;
}

关于里面使用的宏定义,需要读者自己去定义,并通过实验的出一个最优的数字,如果需要笔者完整的数据结构,可自取
https://github.com/yiguang-hack/SA/tree/master

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值