遗传算法求解无约束单目标优化问题(计算智能课程设计)

前面的话

目的是抛砖引玉,不太建议直接抄。

为爱发电,多点赞可能会导致我更新未来你想要的内容。

正文

1、实验目的


理解遗传算法原理,掌握遗传算法的基本求解步骤,包括选择、交叉、变异等,学会运用遗传算
法求解无约束单目标优化问题。


2、背景知识


遗传算法(Genetic Algorithm)是借鉴生物界自然选择、适者生存遗传机制的一种随机搜索方法。
遗传算法模拟了进化生物学中的遗传、突变、自然选择以及杂交等现象,是进化算法的一种。对于一
个最优化问题,一定数量的候选解(每个候选解称为一个个体)的抽象表示(也称为染色体)的种群向更好
的方向解进化, 通过一代一代不断繁衍,使种群收敛于最适应的环境, 从而求得问题的最优解。进化
从完全随机选择的个体种群开始,一代一代繁殖、进化。在每一代中, 整个种群的每个个体的适应度
被评价,从当前种群中随机地选择多个个体(基于它们的适应度),通过自然选择、优胜劣汰和突变产生
新的种群, 该种群在算法的下一次迭代中成为当前种群。传统上, 解一般用二进制表示(0 和 1 组成的
串)。
遗传算法的主要特点是直接对结构对象进行操作,不存在函数求导、连续、单峰的限定; 具有
内在的隐闭并行性和更好的全局寻优能力;采用概率化的寻优方法, 能自动获取和指导优化搜索,自适
应调整搜索方向,不需要确定的规则。遗传算法已被人们广泛地应用于组合优化、机器学习、信号处
理、自适应控制和人工智能等领域中的问题求解,已成为现代智能计算中的一项关键技术。
关键术语:
(1)个体( individuals):遗传算法中所处理的对象称为个体。个体通常可以含解的编码表示形式、
适应度值等构成成分,因而可看成是一个结构整体。其中,主要成分是编码。
(2)种群(population):由个体构成的集合称为种群。
(3)位串(bit string):解的编码表示形式称为位串。解的编码表示可以是 0、 1 二值串、 0~9 十进制
数字串或其他形式的串,可称为字符串或简称为串。位串和染色体(chromosome)相对应。在遗传算法
的描述中,经常不加区分地使用位串和染色体这两个概念。 位串/染色体与个体的关系:位串/染色体一
般是个体的成分,个体还可含有适度值等成分。个体、染色体、位串或字符串有时在遗传算法中可不
加区分地使用。
(4)种群规模(population scale):又称种群大小,指种群中所含个体的数目。
(5)基因(gene):位串中的每个位或元素统称为基因。基因反映个体的特征。同一位上的基因不同,
个体的特征可能也不相同。基因对应于遗传学中的遗传物质单位。在 DNA 序列表示中,遗传物质单位
也是用特定编码表示的。遗传算法中扩展了编码的概念,对于可行解,可用0、 1二值、 0~9十个数字,
以及其他形式的编码表示。例如,在 0、 1 二值编码下,有一个串 S=1011,则其中的 1, 0, 1, 1 这
4 个元素分别称为基因。基因和位在遗传算法中也可不加区分地使用。
(6)适应度(fitness):个体对环境的适应程度称为适应度(fitness)。为了体现染色体的适应能力,通
常引入一个对每个染色体都能进行度量的函数﹐称为适应度函数。
(7)选择(selection):在整个种群或种群的一部分中选择某个个体的操作。
(8)交叉(crossover):两个个体对应的一个或多个基因段的交换操作。
(9)变异(mutation):个体位串上某个基因的取值发生变化。如在 0、 1 串表示下,某位的值从 0 变
为 1, 或由 1 变为 0。
遗传算法的基本流程如下:

本案例意在说明如何使用遗传算法求解无约束单目标优化问题,即求一元函数
𝑓(𝑥) = 𝑥 ⋅ sin(10𝜋 ⋅ 𝑥) + 2
在区间[-1, 2]上的最大值。该函数图像如下

由图像可知该函数在在区间[-1, 2]上有很多极大值和极小值, 对于求其最大值或最小值的问题,
很多单点优化的方法(梯度下降等)就不适合,这种情况下可以考虑使用遗传算法。 


3、 示例代码


1. import numpy as np
2. import matplotlib.pyplot as plt
3.
4.
5. def fun(x):
6. return x * np.sin(10*np.pi*x) + 2
7.
8.
9. Xs = np.linspace(-1, 2, 100)
10.

11. np.random.seed(0)
12.
# 令随机数种子=0,确保每次取得相同的随机数

13. # 初始化原始种群
14. population = np.random.uniform(-1, 2, 10) # 在[-1,2)上以均匀分布生成 10 个浮点数,做为初始种

15.
16. for pop, fit in zip(population, fun(population)):
17. print("x=%5.2f, fit=%.2f" % (pop, fit))
18.
19. plt.plot(Xs, fun(Xs))
20. plt.plot(population, fun(population), '*')
21. plt.show()
22.
23.
24. def encode(population, _min=-1, _max=2, scale=2**18, binary_len=18): # population 必须为
float 类型,否则精度不能保证
25. # 标准化,使所有数据位于 0 和 1 之间,乘以 scale 使得数据间距拉大以便用二进制表示
26. normalized_data = (population-_min) / (_max-_min) * scale
27. # 转成二进制编码
28. binary_data = np.array([np.binary_repr(x, width=binary_len)
29. for x in normalized_data.astype(int)])
30. return binary_data
31.
32.
33. chroms = encode(population) # 染色体英文(chromosome)
34.
35.
36. for pop, chrom, fit in zip(population, chroms, fun(population)):
37. print("x=%.2f, chrom=%s, fit=%.2f" % (pop, chrom, fit))
38.
39.
40. def decode(popular_gene, _min=-1, _max=2, scale=2**18): # 先把 x 从 2 进制转换为 10 进制, 表示
这是第几份
41. # 乘以每份长度(长度/份数) ,加上起点,最终将一个 2 进制数,转换为 x 轴坐标
42. return np.array([(int(x, base=2)/scale*3)+_min for x in popular_gene])
43.
44.
45.
46. fitness = fun(decode(chroms))
47.
48. for pop, chrom, dechrom, fit in zip(population, chroms, decode(chroms), fitness):
49. print("x=%5.2f, chrom=%s, dechrom=%.2f, fit=%.2f" %
50. (pop, chrom, dechrom, fit))
51.
52. fitness = fitness - fitness.min() + 0.000001 # 保证所有的都为正
53. print(fitness)
54.
55.
56. def Select_Crossover(chroms, fitness, prob=0.6): # 选择和交叉
57. probs = fitness/np.sum(fitness) # 各个个体被选择的概率
58. probs_cum = np.cumsum(probs) # 概率累加分布
59.
60. each_rand = np.random.uniform(size=len(fitness)) # 得到 10 个随机数 0 到 1 之间

61.
62. # 轮盘赌,根据随机概率选择出新的基因编码
63. # 对于 each_rand 中的每个随机数,找到被轮盘赌中的那个染色体
64. newX = np.array([chroms[np.where(probs_cum > rand)[0][0]]
65. for rand in each_rand])
66.
67. # 繁殖,随机配对(概率为 0.6)
68.
69. # 这个数字小于 0.6 就交叉,则平均下来应有三组进行交叉,即 6 个染色体要进行交叉
70. pairs = np.random.permutation(
71. int(len(newX)*prob//2*2)).reshape(-1, 2) # 产生 6 个随机数乱排一下分成二列
72. center = len(newX[0])//2 # 交叉方法采用最简单的,中心交叉法
73. for i, j in pairs:
74. # 在中间位置交叉
75. x, y = newX[i], newX[j]
76. newX[i] = x[:center] + y[center:] # newX 的元素都是字符串,直接用+号拼接
77. newX[j] = y[:center] + x[center:]
78. return newX
79.
80.
81. chroms = Select_Crossover(chroms, fitness)
82.
83. dechroms = decode(chroms)
84. fitness = fun(dechroms)
85.
86. for gene, dec, fit in zip(chroms, dechroms, fitness):
87. print("chrom=%s, dec=%5.2f, fit=%.2f" % (gene, dec, fit))
88.
89. # 对比一下选择和交叉之后的结果
90. fig, (axs1, axs2) = plt.subplots(1, 2, figsize=(14, 5))
91. axs1.plot(Xs, fun(Xs))
92. axs1.plot(population, fun(population), 'o')
93. axs2.plot(Xs, fun(Xs))
94. axs2.plot(dechroms, fitness, '*')
95. plt.show()
96.
97. # 输入一个原始种群 1,输出一个变异种群 2 函数参数中的冒号是参数的类型建议符。
98.
99.
100. def Mutate(chroms: np.array):
101. prob = 0.1 # 变异的概率
102. clen = len(chroms[0]) # chroms[0]="111101101 000010110" 字符串的长度=18
103. m = {'0': '1', '1': '0'} # m 是一个字典,包含两对:第一对 0 是 key 而 1 是 value;第二对 1
是 key 而 0 是 value
104. newchroms = [] # 存放变异后的新种群
105. each_prob = np.random.uniform(size=len(chroms)) # 随机 10 个数
106.
107. for i, chrom in enumerate(chroms): # enumerate 的作用是整一个 i 出来
108. if each_prob[i] < prob: # 如果要进行变异(i 的用处在这里)
109. pos = np.random.randint(clen) # 从 18 个位置随机找一个位置,假设是 7
110. # 0~6 保持不变, 8~17 保持不变,仅将 7 号翻转,即 0 改为 1, 1 改为 0。注意 chrom 中字符
不是 1 就是 0
111. chrom = chrom[:pos] + m[chrom[pos]] + chrom[pos+1:]
112. newchroms.append(chrom) # 无论 if 是否成立,都在 newchroms 中增加 chroms 的这个元

113. return np.array(newchroms) # 返回变异后的种群
114.
115.
116. newchroms = Mutate(chroms)
117.
118.
119. def DrawTwoChroms(chroms1, chroms2, fitfun): # 画 2 幅图,左边是旧种群,右边是新种群
120. Xs = np.linspace(-1, 2, 100)
121. fig, (axs1, axs2) = plt.subplots(1, 2, figsize=(14, 5))
122. dechroms = decode(chroms1)
123. fitness = fitfun(dechroms)
124. axs1.plot(Xs, fitfun(Xs))
125. axs1.plot(dechroms, fitness, 'o')
126.
127. dechroms = decode(chroms2)
128. fitness = fitfun(dechroms)
129. axs2.plot(Xs, fitfun(Xs))
130. axs2.plot(dechroms, fitness, '*')
131. plt.show()
132.
133.
134. # 对比一下变异前后的结果
135. DrawTwoChroms(chroms, newchroms, fun)
136.
137. # 上述代码只是执行了一轮,这里反复迭代
138. np.random.seed(0)
139. population = np.random.uniform(-1, 2, 100) # 这次多找一些点
140. chroms = encode(population)
141.
142. for i in range(1000):
143. fitness = fun(decode(chroms))
144. fitness = fitness - fitness.min() + 0.000001 # 保证所有的都为正
145. newchroms = Mutate(Select_Crossover(chroms, fitness))
146. if i % 300 == 1:
147. DrawTwoChroms(chroms, newchroms, fun)
148. chroms = newchroms
149.
150. DrawTwoChroms(chroms, newchroms, fun)


4、实验内容


运行和理解示例代码, 回答下列问题:
1) 代码第 64 行的语义是什么?两个[0]各自代表什么? 最后 newX 有几个元素?
1. newX = np.array([chroms[np.where(probs_cum > rand)[0][0]]
2. for rand in each_rand])
2) 代码第 70 行的语义是什么?为什么要除以 2 再乘以 2? reshape 中的-1 表示什么?
1. pairs = np.random.permutation(
2. int(len(newX)*prob//2*2)).reshape(-1, 2) # 产生 6 个随机数乱排一下分成二列
3) 请结合 Mutate 函数的内容,详述变异是如何实现的。
4) 将代码第 145 行修改为 newchroms = Select_Crossover(chroms, fitness),即不再执行变异,
执行结果有什么不同,为什么会出现这种变化?
5) 轮盘让个体按概率被选择,对于适应度最高的个体而言,虽然被选择的概率高,但仍有可能
被淘汰,从而在进化过程中失去当前最优秀的个体。一种改进方案是,让适应度最高的那个个体不参
与选择,而是直接进入下一轮(直接晋级),这种方案被称为精英选择(elitist selection)。 请修改
Select 部分的代码, 实现这一思路。
6) 【选做】 请借鉴示例代码,实现教材 P57 的例 2.6.1,即用遗传算法求解下列二元函数的最
大值。 注意:不允许分开求解𝑥1 ⋅ sin (4𝜋 ⋅ 𝑥1)和𝑥2 ⋅ sin (20𝜋 ⋅ 𝑥2)的最大值再合并。

题目答案

1) 代码第 64 行的语义是什么? 两个[0]各自代表什么? 最后 newX 有几个元素?
newX = np.array([chroms[np.where(probs_cum > rand)[0][0]]
for rand in each_rand])
答:
这一行的语义简单来说是: 使用轮盘赌来选择接下来染色体交叉的种群编码, 由于每
个个体编码被选择的概率与 fitness(适应程度) 成正比, 被轮盘赌选择中的基因大概率
是适应程度高的个体。
先转换代码:
for rand in each_rand:
newX.append(chroms[np.where(probs_cum > rand)[0][0]])
详细解释一下: 其中的元素含义, newX 是指染色体交叉之后新生成的种群编码, chroms
指的是交叉之前的种群编码, probs_cum 是轮盘上的具体数字。 np.where 函数, 当设立的
条件成立时, where 返回的是每个符合条件元素的坐标,返回的是以元组的形式。
前面说到 where 返回的是元组, 先看一下输出格式:

np.where()[0] 表示行索引, np.where()[1]表示列索引, 若 where 的对象是一个二
维变量, 得到的就将是两个 array, 故, 第一个[0]代表选择的是行索引, 第二个[0]则代
表满足条件的第一个个体编码, 事实上, 因为是叠加得到的 probs_cum, 若是前一个满足,
后面的也将都会满足。
最后 newX 里面有的元素和 chroms 的元素个数一样, 在这里都是 100 个。
2) 代码第 70 行的语义是什么? 为什么要除以 2 再乘以 2? reshape 中的-1 表示什
么?
pairs = np.random.permutation(
int(len(newX)*prob//2*2)).reshape(-1, 2) # 产生 6 个随机数乱排一下分成二列
答:
语义是产生随机不重复的序列对, 为后面随机交叉染色体做准备。
按概率算出来的 len(newX)*prob 需要交叉的染色体个数有可能是奇数, 先//2 再*2
是为了变成偶数, //运算时将只取整数部分, 做到奇数变偶数。
-1 表示行数待计算, 根据列数来计算行数, 也就输将(a,b)矩阵放到(x,c)中, a,b,c
已知, x 也能求出来, 将 x 替代-1。
3) 请结合 Mutate 函数的内容, 详述变异是如何实现的。
答:
设置好变异率, 基因的长度, 变异字典, 变异之后的种群存放变量。
然后每个个体都产生一个[0,1)中的随机数, 当个体对应的随机数小于变异的概率时
会变异, 变异的过程是更改基因中的随机一处编码, 对该编码取反。
变异完成之后逐个放入变异之后的种群存放变量最后返回这个变量成为新的种群。
4) 将代码第 145 行修改为 newchroms = Select_Crossover(chroms, fitness), 即不再
执行变异, 执行结果有什么不同, 为什么会出现这种变化?
答:
未修改:

修改之后:

不变异的执行结果中能够取得的最大值比较低, 且不变异的执行结果都趋近一个点了,
效果不太好。 这是因为当基因不变异之后, 种群的发展就完全只能基于初始种群基因来争
取极大值, 很有可能不是最大值。 对于 18 位编码, 有可能所有个体都缺失最优解的编码,
所以虽然能够取得基于初始种群的极大值, 但不能取得最大值, 需要通过变异来跳出初始
种群的限制, 接近最大值。
5) 轮盘让个体按概率被选择, 对于适应度最高的个体而言, 虽然被选择的概率高, 但仍
有可能被淘汰, 从而在进化过程中失去当前最优秀的个体。 一种改进方案是, 让适应度最
高的那个个体不参与选择, 而是直接进入下一轮(直接晋级), 这种方案被称为精英选择
(elitist selection)。 请修改 Select 部分的代码, 实现这一思路。
答:
def Select_Crossover(chroms, fitness, prob=0.6): # 选择和交叉
#添加代码
index_max = np.argmax(fitness)
maxf=fitness[index_max]
maxc=chroms[index_max]
chroms=np.delete(chroms,index_max)
fitness=np.delete(fitness,index_max)
#结束
probs = fitness/np.sum(fitness) # 各个个体被选择的概率,fitness 越大被选择的可能
性就越大 probs_cum = np.cumsum(probs) # 概率累加分布
each_rand = np.random.uniform(size=len(fitness)) # 得到 10 个随机数, 0 到 1 之间
newX = np.array([chroms[np.where(probs_cum > rand)[0][0]]
for rand in each_rand])
pairs = np.random.permutation(
int(len(newX)*prob//2*2)).reshape(-1, 2) # 产生 6 个随机数, 乱排一下, 分成二

pairs = np.random.permutation(
int(len(newX)*prob//2*2)).reshape(-1, 2) # 产生 6 个随机数, 乱排一下, 分成二

center = len(newX[0])//2
for i, j in pairs:
# 在中间位置交叉
# print("i",i,"j",j)
x, y = newX[i], newX[j]
newX[i] = x[:center] + y[center:]
newX[j] = y[:center] + x[center:]
#添加代码
newX=np.append(newX,maxc)
fitness=np.append(fitness,maxf)#可以省去, 每次都会重新计算
#结束
return newX
先在种群和适应度数据集合中删除最优秀的个体, 然后进行随机选择, 然后再在 newX
和 fitness 中加入最优秀的个体, newX 会成文新的 chroms, 就不再多谢一行了。
6)【选做】 请借鉴示例代码, 实现教材 P57 的例 2.6.1, 即用遗传算法求解下列二元函
数的最大值。 注意: 不允许分开求解再合并

答:
计算 fitness 的函数和画图函数
def newfun(x,y):
return 21.5+x*np.sin(4*x*np.pi)+y*np.sin(20*y*np.pi)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
def draw1(x1,x2,y):
# plt.clf()
print("y",y)
fig = plt.figure()
ax = plt.axes(fc='whitesmoke',
projection='3d'
)
plt.title(u'遗传算法', fontsize=15)
ax.scatter(x1, x2, y,s=80, marker='+',c='r')
A = np.linspace(-2.9, 12.0, 20)# X1
B = np.linspace(4.7, 5.7, 18)# X2
XX, YY = np.meshgrid(A,B)
ax.plot_surface(XX,YY,Z=newfun(XX,YY),color='g',alpha=0.8)
ax.set_xlabel('X1')
ax.set_ylabel('X2')
ax.set_zlabel('maxf(x1,x2)')
plt.show()
对于编码解码, 事实上函数已经设计好了, 只需要改变参数就行, 但是原本的代码中
的解码函数有一处不严谨, 需要将 3 替换成(_max-_max),
def decode(popular_gene, _min=-1, _max=2, scale=2**18):
# 这里的 3 应该改成(_max-_max)
return np.array([(int(x, base=2)/scale*(_max-_min))+_min for x in popular_gene])
对于染色体交叉函数部分, 我的主要目的是, 按照 fitness 的大小选择, 然后对于 x1、
x2 染色体分别进行随机交换, 主要代码是:
def Select_Crossover(chroms_x1,chroms_x2, fitness, prob=0.6): # 选择和交叉
probs = fitness/np.sum(fitness)
probs_cum = np.cumsum(probs)
each_rand = np.random.uniform(size=len(fitness))
newX1 = np.array([chroms_x1[np.where(probs_cum > rand)[0][0]]
for rand in each_rand])
newX2 = np.array([chroms_x2[np.where(probs_cum > rand)[0][0]]
for rand in each_rand])
pairs_x1 = np.random.permutation(
int(len(newX1)*prob//2*2)).reshape(-1, 2)
pairs_x2 = np.random.permutation(
int(len(newX2)*prob//2*2)).reshape(-1, 2)
centerx1=np.random.randint(len(newX1[0]))
centerx2=np.random.randint(len(newX2[0]))
for i, j in pairs_x1:
x, y = newX1[i], newX1[j]
newX1[i] = x[:centerx1] + y[centerx1:]
newX1[j] = y[:centerx1] + x[centerx1:]
x, y = newX2[i], newX2[j]
newX2[i] = x[:centerx2] + y[centerx2:]
newX2[j] = y[:centerx2] + x[centerx2:]
return newX1,newX2
对于变异函数, 不需要改变, 然后对主函数修改:
np.random.seed(4)
population_x1 = np.random.uniform(-2.9, 12., 100) # 这次多找一些点
population_x2 = np.random.uniform(4.2, 5.7, 100) # 这次多找一些点
chroms_x1 = encode(population_x1,_min=-2.9,_max=12,scale=2**21)
chroms_x2 = encode(population_x2,_min=4.2,_max=5.7,scale=2**18)
fitness = newfun(population_x1, population_x2)
maxli=[]
for i in range(1000):
fitness = newfun(decode(chroms_x1,_min=-2.9,_max=12,scale=2**21),
decode(chroms_x2,_min=4.2,_max=5.7,scale=2**18))
maxli=np.append(maxli,max(fitness))
fitness = fitness - fitness.min() + 0.000001 # 保证所有的都为正
newchromsx1,newchromsx2 = (Select_Crossover(chroms_x1,chroms_x2, fitness))
newchromsx1 = Mutate(newchromsx1)
newchromsx2 = Mutate(newchromsx2)
chroms_x1 = newchromsx1
chroms_x2 = newchromsx2
fitness = newfun(decode(chroms_x1, _min=-2.9, _max=12, scale=2 ** 21),
decode(chroms_x2, _min=4.2, _max=5.7, scale=2 ** 18))
draw1(decode(chroms_x1, _min=-2.9, _max=12, scale=2 ** 21),
decode(chroms_x2, _min=4.2, _max=5.7, scale=2 ** 18), fitness)
xx=np.arange(0,1000,1)
plt.clf()
plt.xlabel("次数")
plt.ylabel("fitness 的最大值")
plt.plot(xx,maxli)
plt.show()
得到运行结果, 种群整体的表现

Fitness 的最大值变化:

最后的话

:)看完点赞让我更快更新到你想要的内容。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简遗传算法求解无约束目标优化问题的Python代码: ```python import random # 定义目标函数 def fitness(x): return x**2 - 3*x + 4 # 遗传算法参数设置 pop_size = 50 # 种群大小 num_generations = 100 # 迭代次数 mutation_rate = 0.1 # 变异率 crossover_rate = 0.8 # 交叉率 # 生成初始种群 population = [] for i in range(pop_size): population.append(random.uniform(-10, 10)) # 遗传算法主循环 for generation in range(num_generations): # 计算适应度并排序 fitness_scores = [(fitness(x), x) for x in population] fitness_scores.sort(reverse=True) # 输出当前最优解 print("Generation {}: Best fitness = {:.4f}, Best solution = {:.4f}".format(generation, fitness_scores[0][0], fitness_scores[0][1])) # 选择 elite_size = int(pop_size * 0.2) # 精英数量 parents = [x[1] for x in fitness_scores[:elite_size]] # 选择精英父母 for i in range(pop_size - elite_size): parent1 = random.choice(parents) parent2 = random.choice(parents) # 交叉 if random.random() < crossover_rate: child = (parent1 + parent2) / 2 else: child = parent1 # 变异 if random.random() < mutation_rate: child += random.gauss(0, 1) population[i + elite_size] = child # 输出最终结果 fitness_scores = [(fitness(x), x) for x in population] fitness_scores.sort(reverse=True) print("Final solution: x = {:.4f}, fitness = {:.4f}".format(fitness_scores[0][1], fitness_scores[0][0])) ``` 在这个代码中,我们首先定义了目标函数 `fitness(x)`,然后设置了遗传算法的参数,包括种群大小、迭代次数、变异率和交叉率。接着我们生成了初始种群,然后开始遗传算法的主循环。在每一次迭代中,我们计算了种群中每个个体的适应度,并对它们进行排序,然后输出当前最优解。接着进行选择、交叉和变异操作,生成下一代种群,直到迭代次数达到设定值为止。最后输出最终的最优解。 当然,这只是一个简的示例代码,实际应用中可能需要根据具体问题进行调整和优化

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值