问题描述:
在一个3*3的方棋盘上放置着1,2,3,4,5,6,7,8八个数码,每个数码占一格,且有一个空格。这些数码可以在棋盘上移动,其移动规则是:与空格相邻的数码方格可以移入空格。现在的问题是:对于指定的初始棋局和目标棋局,给出数码的移动序列。该问题称八数码难题或者重排九宫问题。
算法流程图如下所示:
源代码为:
import copy
import numpy as np
import random
import time
import operator
# 遗传算法那解决八数码问题
# 八数码初始化函数,返回一个初始状态和一个目标状态,这里0代表八数码中的空格
def init():
init_state = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8])
target_state = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8])
np.random.shuffle(init_state)
np.random.shuffle(target_state)
init_state = np.reshape(init_state, (3, 3))
target_state = np.reshape(target_state, (3, 3))
return init_state, target_state
# 计算单个状态适应度函数
def fitness_algorithm_single(init_state, target_state):
total_distance = 0
for i in range(1, 9):
(init_row, init_column) = np.where(init_state == i)
(target_row, target_column) = np.where(target_state == i)
total_distance += abs(target_row - init_row) + abs(target_column - init_column)
return int(total_distance)
# 移动并计算移动后的fitness
def move_and_fitness(num, current_state, target_state):
temp_state = copy.deepcopy(current_state)
zero_row, zero_column = np.where(temp_state == 0)
zero_row, zero_column = int(zero_row), int(zero_column)
if num == 0: # 即方向为上,方向顺序为0-3,上下左右
if zero_row - 1 >= 0: # 判断是否还能向上移动,不能出边界,下面同理
up_row, up_column = zero_row - 1, zero_column
temp = temp_state[up_row][up_column]
temp_state[zero_row][zero_column] = temp
temp_state[up_row][up_column] = 0
fitness = fitness_algorithm_single(temp_state, target_state)
return fitness, temp_state
else:
fitness = 10000
return fitness, temp_state
if num == 1:
if zero_row + 1 <= 2:
down_row, down_column = zero_row + 1, zero_column
temp = temp_state[down_row][down_column]
temp_state[zero_row][zero_column] = temp
temp_state[down_row][down_column] = 0
fitness = fitness_algorithm_single(temp_state, target_state)
return fitness, temp_state
else:
fitness = 10000
return fitness, temp_state
if num == 2:
if zero_column - 1 >= 0:
left_row, left_column = zero_row, zero_column - 1
temp = temp_state[left_row][left_column]
temp_state[zero_row][zero_column] = temp
temp_state[left_row][left_column] = 0
fitness = fitness_algorithm_single(temp_state, target_state)
return fitness, temp_state
else:
fitness = 10000
return fitness, temp_state
if num == 3:
if zero_column + 1 <= 2:
right_row, right_column = zero_row, zero_column + 1
temp = temp_state[right_row][right_column]
temp_state[zero_row][zero_column] = temp
temp_state[right_row][right_column] = 0
fitness = fitness_algorithm_single(temp_state, target_state)
return fitness, temp_state
else:
fitness = 10000
return fitness, temp_state
# 计算一个基因序列的最大适应度
# 基因序列每个值都会产生一个新的状态同时也会产生新的适应度,在L长度的基因序列中,算出这个序列最好的适应度
def fitness_algorithm_for_population(init_state, target_state, population_list):
for i in population_list:
temp_state = copy.deepcopy(init_state)
min_fitness = 10000
for j in i:
fitness, temp_state = move_and_fitness(j, temp_state, target_state)
if fitness < min_fitness:
min_fitness = fitness
i.append(min_fitness)
return population_list
# 随机生成解函数,即在遗传算法中随机生成一个基因
def init_solution(L):
"""
:param L: 解的长度,即基因长度
:return:单个解/基因的列表
"""
# list为长度为L,范围为range(0,3)的列表,0-3代表上下左右的移动方向
list = []
for i in range(L):
list.append(random.randrange(0, 4))
return list
# 种群初始化函数
def init_population(M, L):
"""
:param M: 种群大小
:param L: 解的长度,即基因长度
:return: M个种群组成的列表
"""
population_list = []
for i in range(M):
population_list.append(init_solution(L))
return population_list
# 根据适应度计算种群的个体被选作交叉的概率,使用y=-2x+100来调整,并排序
def select_algorithm(population_list):
total = 0
for i in population_list:
fitness = i[-1]
fitness = -1 * fitness + 100
total += fitness
for j in population_list:
fitness = j[-1]
fitness = -2 * fitness + 100
select_prob = fitness / total
j.append(select_prob)
population_list = sorted(population_list, key=lambda key: key[-1], reverse=True)
return population_list
# 优秀的基因直接保留,适应度较低的样本按一定的概率淘汰
def retain_and_eliminate(list, retain_prob, eliminate_prob):
retain_list = []
length = len(list)
# 保留retain_prob百分比的样本
retain_index = int(length * retain_prob)
for i in range(retain_index):
temp = copy.deepcopy(list[i])
retain_list.append(temp)
eliminate_index = int(length * eliminate_prob)
# 因为列表已经按照适应度排序,只需要计算出要淘汰的个数,直接将样本从列表尾部POP掉
for j in range(eliminate_index):
list.pop()
# 将保留的retain_list的最后两位去掉,即曼哈顿距离和被选择的概率
for k in retain_list:
del k[-1]
del k[-1]
return retain_list, list
# 交叉算法,产生新的M个种群
def cross_algorithm(origin_list, M):
new_list = []
for i in range(M):
mother, father = sa_roulette(origin_list)
son = []
split_index = random.randrange(0, len(mother) - 2) # 最后两位是适应度和选择概率
for j in range(split_index):
son.append(mother[j])
for k in range(split_index, len(mother) - 2):
son.append(father[k])
new_list.append(son)
return new_list
# 轮盘赌算法
def roulette_algorithm(list):
total_probability = 0
p = random.random()
for i in list:
total_probability = total_probability + i[-1]
if total_probability > p:
break
return i
# 运行两次轮盘赌得到父母基因,这两个样本应该不同
def sa_roulette(list):
# fix_bug
# 这段代码为了修复种群过小时,经过多代繁衍,所有种群收敛到趋近于一个值,比如10个种群全部适应度为27
# 导致轮盘赌算法陷入无限循环
count = 0
temp = list[0]
for i in list:
if operator.eq(temp, i):
count += 1
if count == len(list):
mother = list[0]
father = list[1]
return mother, father
# *******************************************************************************************
mother = roulette_algorithm(list)
while True:
father = roulette_algorithm(list)
if operator.ne(mother, father):
break
return mother, father
# 基因变异函数
def mutation_algorithm(list, mutation_prob):
for i in list:
for j in range(len(i) - 1):
if random.random() < mutation_prob:
direction = random.randrange(0, 4)
i[j] = direction
return list
# 遗传算法
def SA_algorithm(T=200, M=20, L=50, retain_prob=0.3, eliminate_prob=0.3, mutation_prob=0.3):
"""
:param T: 最大繁衍次数
:param M: 种群个体数量
:param L: 基因长度
:param retain_prob:优秀基因直接保留的比例
:param eliminate_prob: 适应度差的基因淘汰的比例
:param mutation_prob: 变异概率
:return: bool
"""
init_state, target_state = init()
print("初始状态为:\n", init_state)
print("目标状态为:\n", target_state)
population_list = init_population(M, L)
print("种群初始化为")
for j in population_list:
print(j)
for i in range(T):
print("当前是第{0}代".format(i))
population_list = fitness_algorithm_for_population(init_state, target_state, population_list) # 计算适应度
for j in population_list:
print(j)
for i in population_list:
if i[-1] == 0: # 如果存在一个基因它的最小曼哈顿距离为0,则它就是目标解
return True
population_list = select_algorithm(population_list) # 计算被选择概率,并按被选择概率降序排列
print("当前种群状态为:")
for j in population_list:
print(j)
retain_list, population_list = retain_and_eliminate(population_list, retain_prob,
eliminate_prob) # 保留优良基因并淘汰适应度差的基因
print("直接保留的样本为:")
for j in retain_list:
print(j)
print("淘汰后剩下的样本为:")
for j in population_list:
print(j)
population_list = cross_algorithm(population_list, M - len(retain_list)) # 交叉
print("交叉后产生的样本为:")
for j in population_list:
print(j)
population_list = mutation_algorithm(retain_list + population_list, mutation_prob) # 变异
print("变异后产生的样本为")
for j in population_list:
print(j)
return False
def sa_algorithm_test(T, M, L, retain_prob, eliminate_prob, mutation_prob, num):
success_case = 0
fail_case = 0
tic = time.time()
for i in range(num):
if SA_algorithm(T, M, L, retain_prob, eliminate_prob, mutation_prob):
success_case += 1
print("第{0}个例子发现最优解".format(i))
else:
fail_case += 1
print("第{0}个例子没有发现最优解".format(i))
toc = time.time()
print("{0}个例子中成功解决的例子为:{1}".format(num, success_case))
print("{0}个例子成功解决的百分比为:{1}".format(num, success_case / num))
print("{0}个例子中失败的例子为:{1}".format(num, fail_case))
print("{0}个例子失败的百分比为:{1}".format(num, fail_case / num))
print("{0}个例子运行算法所需的时间为:{1}秒".format(num, toc - tic))
sa_algorithm_test(T=100, M=30, retain_prob=0.3, eliminate_prob=0.3, mutation_prob=0.03, num=100, L=200)
在实验参数为:T=300,M=100, retain_prob=0.3, eliminate_prob=0.3, mutation_prob=0.03,L=100条件下,结果为: