”明月如霜,好风如水,清景无限 “
今天做了一个简单的leetcode题。看介绍是用贪心做。题目是:
605. 种花问题
假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。
可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。
给你一个整数数组 flowerbed 表示花坛,由若干 0 和 1 组成,
其中 0 表示没种植花,1 表示种植了花。另有一个数 n ,能否在不
打破种植规则的情况下种入 n 朵花?能则返回 true ,不能则返回 false。
示例 1:
输入:flowerbed = [1,0,0,0,1], n = 1
输出:true
示例 2:
输入:flowerbed = [1,0,0,0,1], n = 2
输出:false
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/can-place-flowers
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
壹
分析一下题目,用贪心的话,还是贪心策略的确定。很明显不需要排序,也没法排。按题目要求转化为比较能种植数和n大小。之后是需要确定已有花朵的位置。问题转化为:如何根据已经种花的位置确定还能种几朵花。看起来是个简单的数学问题。代码如下:
int n_size = flowerbed.size();
vector<int> position;
for (int i = 0; i < n_size; ++i) {
if (flowerbed.at(i) == 1) {
position.push_back(i);
}
}
int num = 0;
int position_size = position.size();
for (auto i = 0; i < position_size; ++i) {
//cout << position[i] << " ";
if (i == 0) {
num += (position[i]) / 2;
}
else {
num += (position[i]- position[i-1]-1) / 2;
}
}
num += (n_size - 1 - position[position_size-1]) / 2;
return num;
思路上清晰,分三断。第一朵花往左是一段,中间一段,最后一朵花往右为一段。
贰
效果很不好,还是思考问题的全面性有待提高。因为没考虑到没有花的时候就只有一段,一朵花时是两段(即if的层次问题,还有就是具体公式计算的细节,eg,三个间隔是只能种1朵花)
if(position_size==0){
bool Flag=(n_size+1)/2>=n ? true:false;
return Flag;
}
比较逗的是在调试时,文远陷入了对原数组情况的分类,因为未通过的栗子是:flowerbed=[1],n=0。这个栗子,我怀疑烂了,,,
if (n_size==1){
if(n<=1&&flowerbed[0]==0||n==0&&flowerbed[0]==1) return true;
else return false;
}
确实折腾了很久,关键时间和空间复杂度好菜啊。
叁
看到这个渣渣后95%的代码。文远决定用de一下GA,也算是回顾一下。先是提供一个GA的模板。
源码:https://gitee.com/AttackerGry/Evolutionary-Algorithm/blob/master/tutorial-contents/Genetic%20Algorithm/Find%20Path.py
b站:https://www.bilibili.com/video/av16926245?zw
好了,之后讲讲GA(Genetic Algorithm)的几个关键点。
整体上看,GA就是在模仿生物学的优胜劣汰多目标优化上,效果很好,由于加入概率,陷入局部最优的可能性不大。用种群理解的话。种群中的每一个个体都是一个解,或者说是一种方案,答案。计算机模拟自然界优胜劣汰,种群一代一代更替后,种群中的个体都收敛到适应度最高的状态。也就是收敛后的种群中的个体对应的结果,方案是最优的。而且所有个体的结果都比较好。
实践关键注意点:
-
初始种群,尽量随机。这样陷入局部最优的解只是一部分,不影响种群的整体进化。
-
我们需要解决的问题,或者说我们定义的优胜略汰的规则,就是GA最关键的地方。习惯上称之为:fitness(适应度)。也就是每一个个体适应环境的能力。
-
常见操作:将一些硬约束的惩罚力度加到最大(也就是让其适应度无穷小)软约束:(某些情况只是局部不优,可能后面又全局最优了)惩罚适应度的力度会小一些。一般都是得分制设计。!!!!要体现出你想要的优化!!!!
这一点和数学相关性很大,但是你可以仔细地学习了解激活函数(如何建立映射),还是很有启发的。一些取指数的技巧啊,有时像ReLu,TanH,这个还是自己好好把握。
下面是常见的三种操作了。
- 复制(copy)
复制就涉及到选择了,选择出本代较优的个体。可以用进化策略,选前10%适应度的个体进入下一代,再复制回100%的个体数。也可以用fitness做抽样概率,放回抽样的情况下,适应度高的个体可能被连续抽到下一代。感觉效果差不多。
- 交叉(crossover)
交叉的操作从DNA层面比较容易理解。最简单直接的情况是一半父代基因一半母亲的基因。这个在某些情况下,极其不适用,也好理解,两个好方案组合一下,很可能就四不像了。(这里必须要提一下编码解码,一般是要把方案编码为数字的,计算机好识别。参考one-hot编码解码),有双亲(两个个体)注意用copy()函数复制一下种群。
- 变异(mutation)
变异是当之无愧的关键一步,从生物演化来说也是如此。一开始很垃圾的种群,经过变异产生一些种群里完全没有的基因。也就是变异让全局最优搜索的能力又加强了。可以因为变异从局部最优的解空间中跳出来。(设计变异多种多样,可以从变异范围,变异数量等多重考虑,但记住是每一个DNA都有可能变异,需要考虑具体的情况)
最后就是经典何时收敛。收敛条件一般设定为本代最高适应度与上代,上上代一样。有些学习幅度大的,不是很稳定时。可以:
abs(fitness[i]-fitness[i-1])/fitness[i] < 1e-2
肆
种花-605实践代码:
import numpy as np
import re
# import matplotlib.pyplot as plt
flowerbed=[]
# flowerbed = [1,0,0,0,1]
print("输入初始土地栽花情况flowerbed数组:")
# flowerbed = [1,0,0,0,1]
flowerbed = [0,0,1,0,1,0,0,1,0,0,0]
# flowerbed = [0,0,0,1,0,0,1,0,0,0,1]
n=int(input('输入栽入花的数量'))
TARGET_PLAN = flowerbed # target DNA
POP_SIZE = 300 # population size
CROSS_RATE = 0.5 # mating probability (DNA crossover)
MUTATION_RATE = 0.1 # mutation probability
N_GENERATIONS = 500
DNA_SIZE = len(TARGET_PLAN)
origin_pop=flowerbed*POP_SIZE
origin_pop=np.array(origin_pop)
origin_pop=origin_pop.reshape(POP_SIZE,-1)
class GA(object):
def __init__(self, DNA_size, mutation_rate, pop_size):
self.DNA_size = DNA_size
# self.cross_rate = cross_rate
self.mutate_rate = mutation_rate
self.pop_size = pop_size
self.pop = origin_pop
def get_fitness(self): # 最重点的计算适应度
###首先是体现出相邻的1是不行的,即硬约束
### 惩罚为 *-1000000
fitness_list=[]
ex='1 1'
for individual in self.pop:
# print(individual)
error_result=re.findall(ex,str(individual),re.S)
loss_num=len(error_result)
# print(loss_num)
###还是选择不对就适应度归0,比较好,便于概率处理
# loss_fit=-1000000*loss_num
###接着是体现出种的越多越好,即硬约束
### self.pop 为300个体
fit_count=sum(individual)
if loss_num>0:
fit_count=0
# fit_count=sum(individual)+loss_fit
fitness_list.append(fit_count)
return np.array(fitness_list)
def select(self):
# fitness = self.get_fitness() + 1e-4 # add a small amount to avoid all zero fitness
###如何挑选出得分前10%的个体,传到下一代
##更新fitness,保证概率为正
# fitness=[i for i in fitness if i<0: i=0]
# print(fitness)
idx = np.random.choice(np.arange(self.pop_size), size=self.pop_size, replace=True, p=fitness/fitness.sum())###即从编号range(pop_size)的个体中有放回的抽样,抽出pop_size个
return self.pop[idx]
# def crossover(self, parent, pop):###求简单,不需要
# if np.random.rand() < self.cross_rate:
# i_ = np.random.randint(0, self.pop_size, size=1) # select another individual from pop
# cross_points = np.random.randint(0, 2, self.DNA_size).astype(np.bool) # choose crossover points
# parent[cross_points] = pop[i_, cross_points] # mating and produce one child
# return parent
def mutate(self, child):###要保证的是只能由0变1,也就是再种树,不能1变0。不改变原来结果
for point in range(self.DNA_size):##每一个点都可能变为1,即原来为1则没突变
if np.random.rand() < self.mutate_rate:
child[point] = 1 # 即此节点变为种树
return child
def evolve(self):
pop = self.select()
# pop_copy = pop.copy()###要保持原样本???
# print('前',pop)
for parent in pop: # for every parent
# child = self.crossover(parent, pop_copy)
child = self.mutate(parent)
parent[:] = child
# print('后',pop)
self.pop = pop
###实例化对象
ga = GA(DNA_size=DNA_SIZE, mutation_rate=MUTATION_RATE, pop_size=POP_SIZE) ###初始化
best_fit=[]
# best_fit=[0,1e-5]
for generation in range(N_GENERATIONS):
fitness = ga.get_fitness()
# print(fitness)
# print(np.argmax(fitness))
best_index = np.argmax(fitness)
best_fit.append( fitness[best_index] )
best_result = ga.pop[np.argmax(fitness)]###返回本代适应度最高个体
print('Gen', generation, ': ', best_result ,'-'*10,'fitness:' , best_fit[generation])
# print('Gen', generation, ': ', best_result)
###终止的指标,很重要很重要,三代没有变化则停止
if generation>=2:
if best_fit[generation] == best_fit[generation-1] and best_fit[generation-1] == best_fit[generation-2]:
break
ga.evolve()
# print(best_fit)
# print(sum(flowerbed))
# print(n)
if best_fit[-1]-sum(flowerbed)<n:
print("False")
else:
print("True")
可以看看注释,记住GA类面向对象的操作,在种群迭代时还是很方便的,这种形式最好保持。因为种花情况的情况的原始值确定了,而且只能由0变1,即此处种花。所以变异这个操作大大简化了。而花不能相邻很明显是硬约束,你可以选择一个很大的负适应度。但因为没有用前10%复制,有放回抽样函数:
np.random.choice(np.arange(self.pop_size),size=self.pop_size,replace=True, p=fitness/fitness.sum())
其中的p对应每个个体抽到下轮的机会。所以将相邻种花的情况适应度定义为0。注意收敛条件导致的generation>=2。
举个好吃的栗子:
需要源码点击阅读原文,如果对你有所帮助的话,记得为公众号添加星标啊。
END
作者:不爱跑马的影迷不是好程序猿
喜欢的话请关注点赞👇 👇👇 👇
壹句: 欲买桂花同载酒