局部搜索算法是一种简单的贪心搜索算法,是解决最优化问题的一种启发式算法,该算法每次从当前解的临近解空间中根据启发函数选择一个最优解(也不一定是最优解)作为当前解,直到达到一个局部最优解。本文以求解八皇后问题来描述爬山法,模拟退火法以及遗传算法。
目录
(1)get_num_of_no_conflict(status)
(2)get_parent(all_status, no_conflict_num)
一、 爬山法
1. 算法描述
爬山法是一个向值增加的方向持续移动的简单循环过程--类似于登高,它将会在到达一个“峰顶”时终止,此时相邻状态中没有比它更高的值。算法不会维护搜索树,因此当前节点的数据结构只需要记录当前状态和它的目标函数值。以八皇后问题来说明爬山法,先在棋盘上每一列随机放置一个皇后,之后根据启发式评估函数h(相互攻击的皇后对的数目),只选择h最小的(即相互攻击的皇后对的数目最少)的邻居状态直到找到解或者无解,这种算法很容易改善一个坏的状态,但也有可能会陷入困境,比如邻居状态的h都不比当前小。
2. 算法实现
(1)获得某一状态的h(相互攻击的皇后对的数目)
输入一个状态,通过判断行和列的关系计算出相互攻击的皇后对的数目,并返回该数目
-
def num_of_conflict(status): # 获取该状态下互相攻击的皇后对数
-
num_of_conflict =
0;
-
for col
1 in range(
0,
7):
-
for col
2 in range(col
1+
1,
8):
-
if (status[col
1] == status[col
2]) \
-
or ((col
2 - col
1) == abs(status[col
1] - status[col
2])) : #判断是否相互攻击
-
num_of_conflict +=
1;
-
return num_of_conflict
(2)获取最优邻居
输入当前状态,获得当前状态下的邻居状态中最好的状态,并返回该状态,如果当前状态已是最优。返回当前状态。
-
def
get_min_num_of_conflict_status(status): #返回该状态status时的最优邻居状态,如不存在,则返回本身
-
min_status =
status
-
for
col in range(0,8): #此处两个循环为遍历56种邻居
-
for
row in range(0, 8):
-
new_status =
status[:]
-
if
status[col] != row: #相等时跳过,此时是皇后位置
-
new_status[col] =
row
-
if
num_of_conflict(new_status) < num_of_conflict(min_status):
-
min_status =
new_status #new_status的相互攻击皇后数小于min_status,所以更min_status
-
elif
num_of_conflict(new_status) == num_of_conflict(min_status) \
-
and num_of_conflict(new_status) != num_of_conflict(status):
-
choose =
random.randint(0, 1)
-
if
choose == 1: #当新状态的h也是最小时,根据概率(0,1)随机决定刷新
-
min_status =
new_status
-
return
min_status
(3)完整代码
-
#爬山法
-
import
random
-
-
-
def num_of_conflict(
status): # 获取该状态下互相攻击的皇后对数
-
num_of_conflict =
0;
-
for col1
in range(
0,
7):
-
for col2
in range(col1+
1,
8):
-
if (
status[col1] ==
status[col2]) \
-
or ((col2 - col1) ==
abs(
status[col1] -
status[col2])) : #判断是否相互攻击
-
num_of_conflict +=
1;
-
return num_of_conflict
-
-
-
-
def get_min_num_of_conflict_status(
status): #返回该状态
status时的最优邻居状态,如不存在,则返回本身
-
min_status =
status
-
for col
in range(
0,
8): #此处两个循环为遍历
56种邻居
-
for row
in range(
0,
8):
-
new_status =
status[:]
-
if
status[col] != row: #相等时跳过,此时是皇后位置
-
new_status[col] = row
-
if num_of_conflict(new_status) < num_of_conflict(min_status):
-
min_status = new_status #new_status的相互攻击皇后数小于min_status,所以更min_status
-
elif num_of_conflict(new_status) == num_of_conflict(min_status) \
-
and num_of_conflict(new_status) != num_of_conflict(
status):
-
choose =
random.randint(
0,
1)
-
if choose ==
1: #当新状态的h也是最小时,根据概率(
0,
1)随机决定刷新
-
min_status = new_status
-
return min_status
-
-
status = [
0,
0,
0,
0,
0,
0,
0,
0]
-
for col
in range(
0,
8): #生成随机八皇后棋盘
-
row =
random.randint(
0,
7)
-
status[col] = row
-
print(
"the initial status: ")
-
print(
status)
-
print(
"the num of conflict: ")
-
print(num_of_conflict(
status))
-
while num_of_conflict(
status) >
0 : #当不为解时
-
new_status = get_min_num_of_conflict_status(
status) #获得当前状态的最优邻居
-
if new_status ==
status: #最优邻居就是自己,证明h已经是最小了
-
print(
"the new status: ")
-
print(
status)
-
print(
"the num of conflict: ")
-
print(num_of_conflict(
status))
-
print(
"can't find a answer!")
-
break
-
status = new_status
-
print(
"the new status: ")
-
print(
status)
-
print(
"the num of conflict: ")
-
print(num_of_conflict(
status))
-
if num_of_conflict(
status) ==
0:
-
print(
"find a answer!")
3. 实验结果
(1)实验失败
观察到最后两次h均为1,证明无法找到更优解,此时已经是局部最优,搜索失败
(2)实验成功
二、 模拟退火法
1. 算法描述
模拟退火算法的内层循环与爬山法类似,只是它没有选择最佳移动,而是随机移动。如果该移动使情况改善,该移动则被接收。否则,算法以某个小于1的概率接收该移动。如果移动导致状态“变坏”,概率则成指数级下降——评估值△E变坏。这个概率也随“温度”T降低而下降:开始T高的时候可能允许“坏的”移动,T越低则越不可能发生。如果调度让T下降得足够慢,算法找到最优解的概率逼近于1。以八皇后问题说明模拟退火法,随机选取56(7*8)个邻居状态中的一个,如果邻居状态的h(相互攻击的八皇后对数)小于或等于当前状态的h,则选择该邻居状态,反之,即邻居状态更差,则以一定概率(概率不断减少直到接近0时结束程序)选择该邻居状态,但T下降的足够慢,即概率下降足够慢,次数足够多,找到最优解的概率接近1。
2. 算法实现
(1)全部代码
-
#模拟退火法
-
import
random
-
import
math
-
-
-
def num_of_conflict(
status): # 获取该状态下互相攻击的皇后对数
-
num_of_conflict =
0;
-
for col1
in range(
0,
7):
-
for col2
in range(col1+
1,
8):
-
if (
status[col1] ==
status[col2]) \
-
or ((col2 - col1) ==
abs(
status[col1] -
status[col2])) : #判断是否相互攻击
-
num_of_conflict +=
1;
-
return num_of_conflict
-
-
-
-
def get_next_num_of_conflict_status(
status, T): #根据递减的T和当前状态返回一个新状态
-
next_status = []
-
-
for col
in range(
0,
8):
-
for row
in range(
0,
8):
-
new_status =
status[:]
-
if
status[col] != row:
-
new_status[col] = row
-
next_status.append(new_status)
-
choose_status =
random.randint(
0,
55) #从
56的邻居任选一个
-
if num_of_conflict(next_status[choose_status]) <= num_of_conflict(
status): #新的状态优于原先的
-
return next_status[choose_status]
-
else: #原先的状态优于新状态
-
E = num_of_conflict(
status) - num_of_conflict(next_status[choose_status])
-
probability =
math.e**(E/T) #概率计算公式
-
choose =
random.randint(
0,
1000)/
1000
-
if choose <= probability: #以一定概率使新的状态取代原先的状态
-
return next_status[choose_status]
-
return
status #返回原状态,不移动
-
-
-
-
-
-
-
-
status = [
0,
0,
0,
0,
0,
0,
0,
0]
-
for col
in range(
0,
8):
-
row =
random.randint(
0,
7)
-
status[col] = row
-
print(
"the initial status: ")
-
print(
status)
-
print(
"the num of conflict: ")
-
print(num_of_conflict(
status))
-
T =
5.0 #初始T,(温度)
-
while num_of_conflict(
status) >
0 : #找不到最优解
-
new_status = get_next_num_of_conflict_status(
status, T) #获取新状态
-
if new_status ==
status: #不移动
-
print(
"E < 0, but no move")
-
else:
-
status = new_status
-
print(
"the new status: ")
-
print(
status)
-
print(
"the num of conflict: ")
-
print(num_of_conflict(
status))
-
if num_of_conflict(
status) ==
0:
-
print(
"find a answer!")
-
T = T *
0.99 # T递减,概率也递减
-
if T <
0.0001: #运行
1077 次,此时认为T接近
0
-
print(
"T = 0, can't find a answer")
-
break
3. 实验结果
(1)试验失败截图
因为T的设置问题,总共运行次数达到1077次,若T == 0, 即T无限小,认为搜索失败,但失败几率比爬山法小很多
(2)实验成功截图
三、 遗传算法
1. 算法描述
遗传算法是模拟达尔文进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法,是随机束搜索的一个变形,通过将两个父状态结合来生成后续。以八皇后问题来说明遗传算法,随机生成K个状态,以一定概率(不相互攻击的皇后对数越多,概率越大)选择两个状态,通过交换前N(0 < N < 8)个位置获得两个新的后代,同时将K个后代继续配对。配对过程可能产生变异,此时实验设置变异概率为10%。K在本次实验设为4.
2. 算法实现
(1)get_num_of_no_conflict(status)
获取不相互攻击的皇后对数
(2)get_parent(all_status, no_conflict_num)
根据特定概率从种群中返回一个父亲
(3)variation(all_status)
变异函数,变异是按照10%的几率进行的,因为存在变异,所以只要运行时间足够长(即杂交代数多),总能找到一个解
(4)inheritance(all_status)
杂交函数,输入为一个种群,输出为一个新种群
(5)完整代码
-
#遗传算法
-
import random
-
-
def get_num_of_no_conflict(status): # 获取该状态下不互相攻击的皇后对数
-
num_of_conflict =
0;
-
for col
1 in range(
0,
7):
-
for col
2 in range(col
1+
1,
8):
-
if (status[col
1] == status[col
2]) \
-
or ((col
2 - col
1) == abs(status[col
1] - status[col
2])) : #判断是否相互攻击
-
num_of_conflict +=
1;
-
return
28 - num_of_conflict #此处是求不相互攻击的
-
-
def get_parent(all_status, no_conflict_num): #按照比例求状态群的某一个状态作为父亲之一
-
choose_parent = random.randint(
0, sum(no_conflict_num) -
1)
-
if choose_parent < no_conflict_num[
0]:
-
return all_status[
0]
-
elif choose_parent >= no_conflict_num[
0] and choose_parent < (no_conflict_num[
0] + no_conflict_num[
1]):
-
return all_status[
1]
-
elif choose_parent >= (no_conflict_num[
0] + no_conflict_num[
1]) \
-
and choose_parent < (no_conflict_num[
0] + no_conflict_num[
1] + no_conflict_num[
2]):
-
return all_status[
2]
-
return all_status[
3]
-
-
-
def variation(all_status): #变异
-
for i in range(
0,
4):
-
col = random.randint(
0,
7)
-
row = random.randint(
0,
7)
-
all_status[i][col] = row
-
return all_status
-
-
def inheritance(all_status): #杂交
-
no_conflict_num =
[]
-
new_all_status =
[]
-
for i in range(
0,
4):
-
no_conflict_num.append(get_num_of_no_conflict(all_status[i]))
-
for t in range(
0,
2): #一次生成两个子代,循环两次
-
father = get_parent(all_status, no_conflict_num);
-
mother = get_parent(all_status, no_conflict_num);
-
while father == mother:
-
mother = get_parent(all_status, no_conflict_num);
-
first_child = father[:]
-
second_child = mother[:]
-
num = random.randint(
0,
6) #各种交换下标
0-num的数,形成子代
-
for i in range(
0, num+
1):
-
first_child[i] = second_child[i]
-
second_child[i] = father[i]
-
new_all_status.append(first_child)
-
new_all_status.append(second_child)
-
return new_all_status #返回新的状态种族
-
-
def find_answer(all_status): #判断该状态种族是否有解
-
for i in range(
0,
4):
-
if get_num_of_no_conflict(all_status[i]) ==
28:
-
print(
"find a answer:")
-
print(all_status[i])
-
return True
-
return False
-
-
-
all_status =
[]
-
for i in range(
0,
4): #随机生成
4个状态,即种族
-
status =
[0, 0, 0, 0, 0, 0, 0, 0]
-
for col in range(
0,
8):
-
row = random.randint(
0,
7)
-
status[col] = row
-
all_status.append(status)
-
print(
"the initial all_status: ")
-
print(all_status)
-
all_status = inheritance(all_status) #杂交
-
while find_answer(all_status) == False: #找不到最优后代(最优解)则一直繁衍
-
whether_variation = random.randint(
1,
10) #
10%变异的几率
-
if whether_variation ==
1:
-
print(
"have a variation,and the all_status:")
-
all_status = variation(all_status)
-
print(all_status)
-
else:
-
all_status = inheritance(all_status) #杂交
-
print(
"the next all_status: ")
-
print(all_status)
3. 实验结果
前面已讲过,只要杂交代数足够多,且存在变异,总能找到一个解的
杂交过程
找到最优解