本实验的问题
个工作将要指派给个工人分别完成,工人 完成工作 的时间为 ,问如何安排可使总的工作时间达到最小. 分别设计爬山算法和 模拟退火算法来解决上述指派问题。
1.设 ,要求随机产生 ;
2.当爬山算法与 SA 算法采用相同的随机初始解和邻域结构时,利用 t 检验来分析两种算法能够获得的最优解质量。
爬山算法
简介
爬山算法是一种简单的贪心搜索算法,该算法每次从当前解的临近解空间中选择一个最优解作为当前解,直到达到一个局部最优解。爬山算法实现很简单,其主要缺点是会陷入局部最优解,而不一定能搜索到全局最优解。假设C点为当前解,爬山算法搜索到A点这个局部最优解就会停止搜索,因为在A点无论向那个方向小幅度移动都不能得到更优的解。
算法步骤
1.随机生成一个问题的解
2.随机选取解 的部分邻域 ,并找出邻域中的解 能使总工作时间 最小
3.如果 ,则 赋值给 并执行步骤2;否则,执行步骤4
4.停止,输出当前的 为问题的解
模拟退火
简介
模拟退火其实也是一种贪心算法,但是它的搜索过程引入了随机因素。在迭代更新可行解时,以一定的概率来接受一个比当前解要差的解,因此有可能会跳出这个局部的最优解,达到全局的最优解。以下图为例,假定初始解为左边蓝色点A,模拟退火算法会快速搜索到局部最优解B,但在搜索到局部最优解后,不是就此结束,而是会以一定的概率接受到右边的移动。也许经过几次这样的不是局部最优的移动后会到达全局最优点D,于是就跳出了局部最小值。
模拟退火中以一定的概率是借鉴了热力学的玻尔兹曼函数,在本问题中, 其中是接受不是更好解的概率,是增量,是温度。不难看出温度一定时,增量越大概率越小;增量一定时,温度越大概率越大。
算法步骤
1.初始化:初始温度(充分大),温度下限(充分小),初始解状态(是算法迭代的起点),每个值的迭代次数L;
2.对做第3至第6步;
3.产生新解,即随机交换两个工人的安排;
4.利计算增量,其中为优化目标;
5.若 , 则接受 作为新的当前解; 否则, 以概率接受作为新的当前解;
6.如果满足终止条件则输出当前解作为最优解,结束程序。
(终止条件通常取为连续若干个新解都没有被接受时终止算法);
7.逐渐减少,且,然后转第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
来源:CSDN
作者:Ackerman2
链接:https://blog.csdn.net/Ackerman2/article/details/103781033