爬山算法与模拟退火算法的实验

为君一笑 提交于 2020-01-26 02:38:23

本实验的问题

nn个工作将要指派给nn个工人分别完成,工人ii 完成工作 jj 的时间为 dijd_{ij},问如何安排可使总的工作时间达到最小. 分别设计爬山算法和 模拟退火算法来解决上述指派问题。

1.设 n=100n=100,要求随机产生 dijd_{ij}

2.当爬山算法与 SA 算法采用相同的随机初始解和邻域结构时,利用 t 检验来分析两种算法能够获得的最优解质量。

爬山算法

简介

爬山算法是一种简单的贪心搜索算法,该算法每次从当前解的临近解空间中选择一个最优解作为当前解,直到达到一个局部最优解。爬山算法实现很简单,其主要缺点是会陷入局部最优解,而不一定能搜索到全局最优解。假设C点为当前解,爬山算法搜索到A点这个局部最优解就会停止搜索,因为在A点无论向那个方向小幅度移动都不能得到更优的解。

算法步骤

1.随机生成一个问题的解x0x_0

2.随机选取解 x0x_0 的部分邻域 x1,x2,x3...xnx_1,x_2,x_3...x_n,并找出邻域中的解 xminx_{min} 能使总工作时间 y=f(x)y = f(x) 最小

3.如果 f(xmin)<=f(x0)f(x_{min}) <= f(x_0),则 xminx_{min} 赋值给 x0x_0 并执行步骤2;否则,执行步骤4

4.停止,输出当前的 xminx_{min} 为问题的解

模拟退火

简介

模拟退火其实也是一种贪心算法,但是它的搜索过程引入了随机因素。在迭代更新可行解时,以一定的概率来接受一个比当前解要差的解,因此有可能会跳出这个局部的最优解,达到全局的最优解。以下图为例,假定初始解为左边蓝色点A,模拟退火算法会快速搜索到局部最优解B,但在搜索到局部最优解后,不是就此结束,而是会以一定的概率接受到右边的移动。也许经过几次这样的不是局部最优的移动后会到达全局最优点D,于是就跳出了局部最小值。

Alt

模拟退火中以一定的概率是借鉴了热力学的玻尔兹曼函数,在本问题中p(Δf)=exp(Δf/T)p(Δf) = exp(-Δf/T), 其中pp是接受不是更好解的概率,ΔfΔf是增量,TT是温度。不难看出温度一定时,增量越大概率越小;增量一定时,温度越大概率越大。

算法步骤

1.初始化:初始温度TT(充分大),温度下限TminT_{min}(充分小),初始解状态xx(是算法迭代的起点),每个TT值的迭代次数L;

2.对l=1,2,...,Ll=1,2,...,L做第3至第6步;

3.产生新解xnewx_{new},即随机交换两个工人的安排;

4.利计算增量Δf=f(xnew)f(x)Δf=f(x_{new})−f(x),其中f(x)f(x)为优化目标;

5.若 Δf0Δf\leq0, 则接受 xnewx_{new} 作为新的当前解; 否则, 以概率exp(Δf/T)exp(−Δf/T)接受xnewx_{new}作为新的当前解;

6.如果满足终止条件则输出当前解作为最优解,结束程序。
(终止条件通常取为连续若干个新解都没有被接受时终止算法);

7.TT逐渐减少,且T>TminT>T_{min},然后转第2步

代码

cpp代码

#include <iostream>
#include <vector>
#include <stdlib.h>    
#include <time.h> 

#define HILLNERBOR 500  // 爬山算法 随机遍历的邻居数
#define SAINNERLOOP 500 // SA 内循环次数
using namespace std;
// 工人的效用矩阵
vector<vector<int>> getUtility() {
	vector<vector<int>> utility; 
	srand((unsigned)time(NULL));
	const int MIN = 1;   //随机数产生的范围    
	const int MAX = 100;
	vector<int> ur;


	for (int j = 0; j < 100; j++)
	{
		for (int i = 0; i < 100; i++) {
			ur.push_back(MIN + rand() % (MAX + MIN - 1));
		}
		utility.push_back(ur);
		ur.clear();
	}
	return utility;
}
// 随机的初始解
vector<int> randperm() {
	vector<int> idx;
	const int MIN = 0;   //随机数产生的范围    
	const int MAX = 99;
	int changeIdx;
	srand((unsigned)time(NULL));
	for (int i = 0; i < 100; i++) // idx = 0:1:99
		idx.push_back(i);
	for (int& x : idx) {
		changeIdx = MIN + rand() % (MAX + MIN - 1);
		swap(idx[x],idx[changeIdx]);
	}
	return idx;
}  
// 计算总时间
int getTotalTime(const vector<int>& plan, const vector<vector<int>>& utility) {
	int res = 0;
	for (int i = 0; i < 100; i++)
		res += utility[i][plan[i]];
	return res;
}
// 爬山算法寻找邻居
int findMinNeighbour(vector<int>& plan, const vector<vector<int>>& utility, int min_time) {
	int temp;
	vector<int> min_plan = plan;
	  //  if 暴力搜索 所有邻域!! 4950 次!
	const int MIN = 0;   //随机数产生的范围    
	const int MAX = 99;
	srand((unsigned)time(NULL));
	for (int i = 0; i < HILLNERBOR; i++) {
		int changeIdx1 = MIN + rand() % (MAX + MIN - 1);
		int changeIdx2 = MIN + rand() % (MAX + MIN - 1);
		vector<int> ch_plan = plan;
		swap(ch_plan[changeIdx1], ch_plan[changeIdx2]);
		temp = getTotalTime(ch_plan, utility);
		if (temp < min_time) {
			min_time = temp;
			min_plan = ch_plan;
		}
	}
	plan = min_plan;// 修改上级函数中的 plan;
	return min_time;
}
// 爬山算法的 main 
double climbHillAlgorithm(int min_time, vector<int> plan, const vector<vector<int>>& utility) {
	//// 爬山算法
	int lastone = min_time;
	for (int i = 0; i < 100; i++) {

		min_time = findMinNeighbour(plan, utility, min_time);// 每次调用 plan会变
	//	cout << "爬山算法的第: " << i << "次:" << min_time << endl;
		if (min_time == lastone) break;
		lastone = min_time;
	}
	cout << "这是爬山算法.....总时间: " << min_time << endl;
	return (double)min_time;
}
// 模拟退火算法
double SA(int min_time_SA, vector<int> plan_SA, const vector<vector<int>>& utility) {
	// 模拟退火
	srand((unsigned)time(NULL));
	const int MIN = 0;   //随机数产生的范围    
	const int MAX = 99;
	double history_min = INT_MAX;
	// 初始化温度
	double T = 100;
	while (T > 0.1)
	{

		for (int i = 0; i < SAINNERLOOP; i++)
		{
			double temp;

			int changeIdx1 = MIN + rand() % (MAX + MIN - 1); // 随机选取邻居
			int changeIdx2 = MIN + rand() % (MAX + MIN - 1);
			swap(plan_SA[changeIdx1], plan_SA[changeIdx2]);
			temp = getTotalTime(plan_SA, utility);
			if (temp <= min_time_SA)
			{
				min_time_SA = temp;
				//cout << "无条件转移啦,总时间:" << min_time_SA << endl;
				if (temp < history_min) history_min = temp;
			}
			else
			{
				double probility = exp((temp - min_time_SA) / (-T));
				double randnum = (rand() % 1000 / (double)1000);
				if (randnum < probility)
				{
					min_time_SA = temp;
					//cout << "随机数是: "<<randnum <<" < "<< probility <<"..总时间:" << min_time_SA << endl;
				}
				else {
					swap(plan_SA[changeIdx1], plan_SA[changeIdx2]);//换回来
					//cout << "不接受,总时间:" << min_time_SA << endl;
				}
			}

		}
		T -= 1;
	}
	cout << "SA算法的历史最小: " << history_min << endl; // 历史最小: 537
	return history_min;
}
double ttext(vector<int> v1, vector<int> v2) {
	double sum1=0, sum2=0,mean1,mean2,s1=0,s2=0,t=0,n1= v1.size(),n2= v2.size();
	for (auto x : v1) sum1 += x;
	for (auto x : v2) sum2 += x;
	mean1 = sum1 / n1;
	mean2 = sum2 / n2;
	for (auto x : v1) s1 += (x - mean1) * (x - mean1);
	s1 /= (n1 - 1);
	for (auto x : v2) s2 += (x - mean2) * (x - mean2);
	s2 /= (n2 - 1);
	t = (mean1 - mean2) / sqrt((n1-1) * s1 + (n2-1) * s2 / (n1+ n2-2)) * (1/n1+1/n2);
	return t;
}
int main() {
	vector<vector<int>> utility;  // 100 * 100 
	vector<int> plan,v1,v2;
	
	utility = getUtility();
	plan = randperm();
	int min_time = getTotalTime(plan, utility);
	double min_time_SA = min_time,res1,res2;
	for (int i = 0; i < 30; i++) {   // 取30次循环实验
		res1 = climbHillAlgorithm(min_time, plan, utility);
		v1.push_back(res1);
		res2 = SA(min_time, plan, utility);
		v2.push_back(res2);
	}
	// T 检验
	double Tvalue;
	Tvalue = ttext(v1, v2);
	cout << "T值为: " << Tvalue << endl;
	cout << "p值取0.05时,样本空间为30时,查表得到T值为1.6973" << endl;
	if (Tvalue > 1.6973) cout << "在95%的置信水平上,两种方法有显著差异" << endl;
	else
		cout << "在95%的置信水平上,两种方法有显著差异" << endl;
	return 0;
}

python代码

# -*- coding: utf-8 -*-
"""
@author: Ackerman
Last eidt on Oct 3 13:16:00 2019
"""
import numpy as np
from scipy import stats

def getTotalTime(utility, plan):
    return np.sum(utility[list(range(100)),plan])


def hillClimb(utility, plan):
    historyMinTime = np.inf
    for j in range(150):
        NerMinTime = historyMinTime  # 寻找邻域最小
        NerMinplan = plan.copy()
        innerLoop = 100
        for i in range(innerLoop):
        #    lp +=1
            idx1, idx2 = np.random.randint(low = 0, high = 100,size = (2,))
            ch_plan = plan.copy()
            ch_plan[idx1], ch_plan[idx2] = ch_plan[idx2], ch_plan[idx1]
            temp = getTotalTime(utility, ch_plan)
    
            if temp <= NerMinTime:
                NerMinTime = temp.copy()
                NerMinplan = ch_plan.copy()
                
        if NerMinTime < historyMinTime:
            plan = NerMinplan.copy()     
            historyMinTime = NerMinTime.copy()
        else:
            break  
    return historyMinTime

def SA(utility, plan):
    historyMinTime = np.inf

    T = 100.0 # 温度初始化
    min_time = historyMinTime # 暂存初始化
    innerLoop = 100
    #lp = 0
    while T > 0.1:
        for i in range(innerLoop):
        #    lp +=1
            idx1, idx2 = np.random.randint(low = 0, high = 100,size = (2,))
            plan[idx1], plan[idx2] = plan[idx2], plan[idx1]
            temp = getTotalTime(utility, plan)
            if temp <= min_time:
                min_time = temp.copy() #   无条件转移
               # print("无条件转: ",min_time)
                if temp < historyMinTime: 
                    historyMinTime = temp.copy()
                    #print("历史最小: ",historyMinTime)
            else:
                proility = np.exp((temp - min_time)/(-T))
                if np.random.rand() < proility:
                    min_time = temp.copy()
                    #print("概率接受: ",min_time)
                else:
                    plan[idx1], plan[idx2] = plan[idx2], plan[idx1]
           # if  lp % 5000 == 1:print("此时历史最小: ",historyMinTime)
        T *= 0.9  
    return historyMinTime

res2 = [];res1 = []
for i in range(30):
    utility = np.random.randint(low = 1, high = 101, size = (100,100))  # 100 * 100 的矩阵 数值从[1,100]中随机取 
    plan = np.arange(100)
    np.random.shuffle(plan)
    res1.append(hillClimb(utility, plan))
    print("爬山的最小: ",res1[-1])  
    res2.append(SA(utility, plan))
    print("SA的最小: ", res2[-1])
if stats.ttest_ind(res1,res2,equal_var=False).pvalue < 0.05:
    print("两方法有显著差异")

参考链接

[1] https://blog.csdn.net/zhouzi2018/article/details/82118673

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!