布谷鸟搜索算法-Cuckoo Search

目录

前言

优缺点

算法原理

应用场景

使用布谷鸟算法解决作业车间调度问题(JSP)

问题描述

作业车间调度在本系统中需要考虑如下约束:

问题的编码与解码

程序流程

 实现代码

 算例测试

计算结果

算法伪代码

代码

MATLAB

Java

Python


前言

布谷鸟搜索算法(Cuckoo Search,缩写 CS)是由剑桥大学杨新社教授和S.戴布于2009年提出的一种新兴启发算法。根据昆虫学家的长期观察研究发现,一部分布谷鸟以寄生的方式养育幼鸟,它们不筑巢,而是将自己的卵产在其他鸟的巢中(通常为黄莺、云雀等),由其他鸟(义亲)代为孵化和育雏。然而,如果这些外来鸟蛋被宿主发现,宿主便会抛弃这些鸟蛋或新筑鸟巢。

CS算法通过模拟某些种属布谷鸟(CuckooSpecies)的寄生育雏(BroodParasitism)来有效地求解最优化问题的算法.同时,CS也采用相关的Levy飞行搜索机制。研究表明,布谷鸟搜索比其他群体优化算法更有效.

算法源英文论文参见【智能优化算法】布谷鸟搜索算法-Cuckoo Search源论文

优缺点

算法思想主要基于两个策略:布谷鸟的巢寄生性和莱维飞行机 制。通过随机游走的方式搜索得到一个最优的鸟窝来孵化自己的鸟蛋,这种方式可以达到一种高效的寻优模式。

CS算法主要优点是参数少、操作简单、易实现、随机搜索路径优和寻优能力强等,备受学者关注,相关的科研成果也日益倍增。王凡、贺兴时等已在文献中通过建立CS算法的Markov链模型,理论证明了该算法可收敛于全局最优。CS算法的衍生算法以及应用研究也已得到了快速的发展,但国内外对CS算法的综述性研究比较少,YANG等在文献中对CS算法最初的发展和它的多种改进算法之间进行了比较,没有详细概述CS算法的发展现状。因此,有必要对CS算法的原理、算法改进、其各领域的应用、算法优缺点、使用范围、存在的问题以及下一阶段的研究方向等进行系统、全面的总结和评述,进而呈现CS算法的发展现状,期望该算法能够解决更多更有效的实际问题。

CS算法与遗传算法(GA)、蚁群算法(ACO)、粒子群算法(PSO)、蜂群算法(ABC)等均属于群只能优化算法,它们皆为基于种群,借助迭代来实现优化步骤的概率搜索算法。总结与对比了这5种算法的优点、缺点以及适合求解的问题,结果如表1所示:
表1 CS于GA、ACO、PSO、ABC算法的比较

虽然GA、ACO、PSO和ABC的研究及应用比较成熟,但从表1可知,CS算法在参数数目、全局寻优能力等方面综合优势更大,算法可灵活地跟其他算法进行多种组合,并具有更广泛的适用性。CS算法作为后起之秀,它的优越性使其广泛应用于各个研究领域。

  • 优点是 全局寻优能力强,参数少且易于实现,易与其他算法相结合等综合优势,
  • 缺点是 存在收敛速度慢、进化后期种群多样性差等不足。CS算法收敛速度偏慢、求解精度较低:CS算法通过莱维飞行机制寻找鸟巢,莱维飞行是一种由小步长的短距离飞行和偶尔大步长的长距离飞行组成的随机游走过程,因此布谷鸟的寻窝路径容易在不同的搜索区域间跳跃,导致布谷鸟算法的局部精细搜索能力较差,在算法迭代后期容易在全局最优解附近的区域出现震荡现象,造成算法效率偏低群多样性差等不足

算法原理

在自然界中,布谷鸟寻找适合自己产卵的鸟窝位置是随机的或是类似随机的方式,为了模拟布谷鸟寻窝的方式,首先,需要设定以下3个理想的状态

(1)布谷鸟一次只产一个卵,并随机选择鸟窝来孵化它;

(2)在随机选择的一组鸟窝中,最好的鸟窝将会被保留到下一代;

(3)可利用的鸟窝数量n是固定的,一个鸟窝的主人能发现一个外来鸟蛋的概率Pa∈[0,1],在这3

个理想状态的基础上,布谷鸟寻窝的路径和位置更新公式如下:

x(t+1)i=x(t)i+α*L(λ),i=1,2,…,n.(1)

其中x(t)i表示第i个鸟窝在第t代的鸟窝位置,*为点对点乘法,α表示步长控制量,L(λ)为Levy随机搜索路径,并且L~u=t-λ,(1pa,则对x(t+1)i进行随机改变,反之不变。最后保留测试值较好的一组鸟窝位置y(t+1)i,此时仍把y(t+1)i记为x(t+1)i。

算法流程图 

应用场景

使用布谷鸟算法解决作业车间调度问题(JSP)


下面介绍布谷鸟算法在作业车间调度问题上的实际应用。

问题描述


作业车间调度问题(Job Shop Scheduling, JSP)是最经典的几个NP-hard问题之一。其应用领域极其广泛,涉及航母调度,机场飞机调度,港口码头货船调度,汽车加工流水线等。

作业车间调度在本系统中需要考虑如下约束:


本问题要求算法目标为所有作业加工完成总时间最短

问题的编码与解码


若由n个工件,所有工件最多需要m道工序完成,则JSP的一个可行序列可以被描述成一个n ∗ m 的整数串。本文先通过一定的操作生成一系列初始编码值,后通过对于编码值进行解码来获取满足约束的可行解序列,通过对于可行解序列的好坏适应度情况评估来对编码值进行算法操作。

下面使用一个n = 2 ,m = 3 的示例来对编码和解码进行说明。

首先在定义域内随机生成一个 2 ∗ 3 的实数组如( 0.3 , 0.2 , 0.8 , 0.1 , 0.6 , 0.4 )并对此这些元素按大小排序。规定数值小的顺序靠前且最小的数编号为0,则此时排序结果为:

[2,1,5,0,4,3]

将此数组每个元素分别除以最大工序数 m ,并取下整。则可得到一组可行序列 A :

A =[0,0,1,0,1,1]
显然,可行序列 A 的加工路线为工件0的第1个工序,工件0的第2个工序,工件1的第1个工序,工件0的第3个工序,工件1的第2个工序,工件1的第3个工序。

如此,我就就通过一个可操作的实数序列转换成一个满足约束条件的工序。

但对于所有工件,其的操作序列可能最大工序次数小于m,我们只需将其正常加入,对于缺少的工序数量我们添加相同数量的且持续时间为0的无关工序即可,在进行计算工序时间时忽略这些持续时间为0的工序即可。

程序流程


算法流程基本同上述部分中描述基本一致,现描述如下:

 实现代码


   
   
  1. package jspNew;
  2. import java.util. *;
  3. import org.apache.commons.math 3.special.Gamma;
  4. / *
  5. * 重点关注test类中的myRead方法中对于数据读入的部分。
  6. * 同时注意在Data类中reVec方法对于结果的返回。
  7. * 全局最优解 在主流程类Run中的run方法的ansCost中 全局最优机器任务序列,由reVec方法返回
  8. */
  9. class test{
  10. public static void myRead() {
  11. Scanner cin = new Scanner(System. in);
  12. / /全局定义
  13. Data.n = cin.nextInt(); / /TODO 此处需要读取n个工件
  14. Data.m = cin.nextInt(); / /TODO 此处需要读取最大工序数 每个工件最多有m个工序
  15. Pair[][] t = new Pair[ Data.n][ Data.m]; / /暂存的任务列表
  16. Bugu.n = cin.nextInt(); / /TODO 此处需要读取种群数量
  17. Bugu.step = cin.nextInt(); / /TODO 此处需要读取迭代次数
  18. / /t[ 0][ 0] = Pair.make_pair( 1, 3);
  19. / /读取数据
  20. for(int i = 0 ; i < Data.n ; i + +) {
  21. int tm = cin.nextInt(); / /TODO 此处需要读取第i个工件的工序数
  22. for(int j = 0 ; j < Data.m ; j + +) {
  23. if(j >= tm)
  24. t[i][j] = Pair.make_pair( 0, 0);
  25. else {
  26. int a = cin.nextInt(),b = cin.nextInt(); / /TODO a是第i个工件 在第j道工序 需要在 a机器上执行 b时间
  27. t[i][j] = Pair.make_pair(a, b);
  28. }
  29. }
  30. }
  31. / /创建全局工件,并将数组赋值(从暂存的任务列表中获取)
  32. Data.init( Data.n);
  33. for(int i = 0 ; i < Data.n ; i + +) {
  34. Data.myData[i] = Data. get_ data(t[i]);
  35. }
  36. / / int[] tans = new int[]{ 1,2,2,2,0,0,1,0,1};
  37. / / System.out.println( Data.getCost(tans));
  38. }
  39. }
  40. / /实际上是任务类
  41. class Pair{
  42. public int machine, time;
  43. Pair(int machine,int time){
  44. this.machine = machine;
  45. this. time = time;
  46. }
  47. / /工厂模式
  48. static Pair make_pair(int machine,int time) {
  49. return new Pair(machine, time);
  50. }
  51. }
  52. / / 2020711
  53. class three extends Pair{
  54. int third;
  55. three(int a,int b,int c){
  56. super(a, b);
  57. third = c;
  58. }
  59. static three make_three(int a,int b,int c) {
  60. return new three(a,b,c);
  61. }
  62. }
  63. class Data{
  64. public static Data[] myData; / /全局工件
  65. public static int n,m; / /全局工件数和最大任务数 任务数量
  66. public static double Myrandom() {
  67. return Math. random();
  68. }
  69. public static void init(int nn) {
  70. n = nn;
  71. myData = new Data[nn];
  72. }
  73. public Pair[] task; / /每个工件的任务序列
  74. public static Data get_ data(Pair[] t) { / /返回了一个工件
  75. Data tt = new Dat a();
  76. tt.task = new Pair[m];
  77. for(int i = 0 ; i < t. length ; i + +) {
  78. tt.task[i] = t[i];
  79. }
  80. return tt;
  81. }
  82. public static int getCost(int[] t) {
  83. Map <Integer,Integer > map = new HashMap <Integer,Integer >(); / /记录每个工件做到了第几个步骤。
  84. int[] lastRun = new int[n]; / /每个工件上个任务的完成时间。
  85. int[] lastMachine = new int[m]; / /每个机器上个工件的完成时间。
  86. for(int i = 0; i < n ;i + +) {
  87. map.put(i, 0); / /一开始都是第 0个步骤。
  88. }
  89. int ans = - 1;
  90. for(int i = 0 ; i < t. length ; i + +) {
  91. int now = t[i]; / /获取当前的工件编号
  92. int pairNum = map. get(now); / /获取工件编号的步骤。
  93. map.remove(now); / /移除工件上一个工件编号。
  94. map.put(now, pairNum + 1); / /更新工件做到的步骤
  95. Pair nowPair = myData[now].task[pairNum]; / /获取当前工件的步骤的任务。
  96. int nowMachine = nowPair.machine; / /获取任务机器。
  97. int nowCost = nowPair. time; / /获取任务时间。
  98. if(nowCost = = 0)
  99. continue;
  100. int newTime = Math.max(lastMachine[nowMachine], lastRun[now]) + nowCost; / /从当前工件上一个任务结束时间
  101. / /和待放入机器的最后一个工件完成时间中挑个大的。
  102. lastMachine[nowMachine] = lastRun[now] = newTime;
  103. / / System.out.println( "newTime:" + newTime
  104. / / + " nowMachine:" + nowMachine +
  105. / / " nowCost:" + nowCost);
  106. ans = Math.max(ans, newTime); / /获取最长时间
  107. }
  108. return ans;
  109. }
  110. / / 2020711
  111. static Vector <three >[] reVec(int[] in){
  112. Vector <three >[] t = new Vector[n *m];
  113. int[] vis = new int[n]; / /工序数
  114. int[] machineLastTime = new int[m];
  115. int[] taskLastTime = new int[n];
  116. for(int i = 0 ; i < t. length ; i + +) {
  117. t[i] = new Vector <three >();
  118. }
  119. for(int i = 0 ; i < in. length ; i + +) {
  120. int aim = in[i];
  121. int index = vis[aim] + +;
  122. int machine = myData[aim].task[ index].machine;
  123. / /System.out.println(machine);
  124. int time = myData[aim].task[ index]. time;
  125. / /System.out.println( time);
  126. if( time = = 0)
  127. continue;
  128. int maxStartTime = taskLastTime[aim] > machineLastTime[machine] ?
  129. taskLastTime[aim] : machineLastTime[machine];
  130. / * System.out.println( "aim:" +aim + "\n" + "index:" + index +
  131. "\n" + "machine:" +
  132. machine + "\n" + "time:" + time + "\n" + "taskLastTime:" +taskLastTime[aim]
  133. + "\n" + "machineLastTime" +machineLastTime[machine]
  134. + "\n-------------------------"); * /
  135. taskLastTime[aim] = machineLastTime[machine] = maxStartTime + time;
  136. t[machine]. add(three.make_three(maxStartTime, taskLastTime[aim],aim));
  137. }
  138. / / test
  139. / /System.out.println(m);
  140. for(int i = 0 ; i < m ; i + +) {
  141. System.out.print( "machine " +i + ":" + '\n');
  142. for(int j = 0 ; j < t[i]. size() ; j + +)
  143. System.out.println( "task :" + t[i]. get(j).third + " start:" + t[i]. get(j).machine + " end:" + t[i]. get(j). time);
  144. }
  145. / /TODO 返回了一个Vector数组,以 机器编号为 vector数组下标索引
  146. / /对于每个Vector,中存放了若干个三元组,表示一个机器的 任务序列
  147. / /对于每个任务序列中的每个任务 以 <工件名称,开始任务时间,结束任务时间 >储存,
  148. / /三元组的数据结构为three,可见代码中的three部分。
  149. return t;
  150. }
  151. }
  152. class see{
  153. / /解码,将编码解码成可行任务序列。
  154. public static int[] recode(double[] tt) {
  155. double[]t = new double[tt. length]; / /临时数组,防止原数组被改。
  156. for(int i = 0 ; i < t. length ; i + +)
  157. t[i] = tt[i];
  158. Map <Double,Integer > map = new HashMap <Double,Integer >(); / /记录以下数组编号。
  159. for(int i = 0 ; i < t. length ; i + +)
  160. map.put(t[i], i);
  161. Arrays. sort(t); / /排个序
  162. int[] ans = new int[t. length];
  163. for(int i = 0 ; i < t. length ; i + +) {
  164. ans[map. get(t[i])] = i; / /将排序后的顺序填入原来的位置
  165. }
  166. for(int i = 0 ; i < t. length ; i + +) {
  167. int t 1 = (int)((double)ans[i] / (double) Data.m);
  168. ans[i] = t 1;
  169. }
  170. / / for(int i = 0 ; i < ans. length ; i + +)
  171. / / System.out.println(ans[i]);
  172. / / System.out.println();
  173. return ans;
  174. }
  175. / /得到一个从u到v的正态分布
  176. public static double NormalDistribution(double u,double v){
  177. java.util. Random random = new java.util. Random();
  178. return Math.sqrt(v) * random.nextGaussian() +u;
  179. }
  180. / /得到伽马函数
  181. public static double getGamma(double t) {
  182. return Gamma.gamma(t);
  183. }
  184. / /获取u分布
  185. static double u,beta = 1.5;;
  186. private static void getU(){
  187. u = (getGamma( 1.0 + beta) *Math.sin((Math.PI * beta) / 2.0) )
  188. / (getGamma((( 1.0 + beta) / 2.0) * beta * Math.pow( 2.0, (beta - 1.0) / 2.0)));
  189. }
  190. / /获取步长
  191. public static double getS() {
  192. getU();
  193. double newu = NormalDistribution( 0,u);
  194. double newv = Math.pow(Math.abs(NormalDistribution( 0,1)), 1.0 /beta);
  195. return newu /newv;
  196. }
  197. public static double randomWalk() {
  198. return 1 * 1 * getD();
  199. }
  200. / /获取方向 https: / /blog.csdn.net /sj 2050 /article /details / 98496868#_ 43
  201. public static double getD() {
  202. double r = (int) Data.Myrandom() * ( 100) + 1;
  203. if(r < 50) return - 1.0;
  204. return 1.0;
  205. }
  206. static public void seeBest(double[] t,int bestCost) {
  207. System.out.println( "本次布谷鸟找到的最优解是:" + bestCost);
  208. System.out.println( "对应的序列是:");
  209. int[] ansShow = see.recode(t);
  210. for(int i = 0 ; i < ansShow. length ; i + +) {
  211. System.out.printf( "%3d ", ansShow[i]);
  212. }
  213. System.out.println();
  214. }
  215. static public void seeIndex(double[] t) {
  216. int[] ansShow = see.recode(t);
  217. for(int i = 0 ; i < ansShow. length ; i + +) {
  218. System.out.printf( "%3d ", ansShow[i]);
  219. }
  220. System.out.println();
  221. }
  222. }
  223. class Bugu{
  224. static public int n;
  225. public double []ans;
  226. static public int step;
  227. Bugu(int n){
  228. ans = new double[n];
  229. for(int i = 0 ; i < n ; i + +) {
  230. ans[i] = Data.Myrandom();
  231. }
  232. }
  233. static public void seeBugu(Bugu[] a) {
  234. for(int i = 0 ; i < a. length ; i + +) {
  235. System.out.print( "bugu[" +i + "]:");
  236. for(int j = 0 ; j < a[i].ans. length ; j + +) {
  237. System.out.print(a[i].ans[j] + " ");
  238. }
  239. System.out.println();
  240. }
  241. System.out.println();
  242. }
  243. }
  244. class run {
  245. public static void Run() {
  246. test.myRead();
  247. / /Bugu.n = 4;
  248. int[] cost = new int[Bugu.n];
  249. Bugu[] bugu = new Bugu[Bugu.n];
  250. double[] bestans = new double[ Data.m * Data.n];
  251. int ansCost = 0x 3f 3f 3f;
  252. for(int i = 0 ; i < Bugu.n ; i + +) {
  253. bugu[i] = new Bugu( Data.n * Data.m);
  254. / /System.out.println();
  255. cost[i] = Data.getCost(see.recode(bugu[i].ans));
  256. / /System.out.println(cost[i]);
  257. }
  258. int bestIndex = - 1,bestCost = 0x 3f 3f 3f;
  259. for(int i = 0 ; i < cost. length ; i + +)
  260. if(cost[i] < bestCost) {
  261. bestCost = cost[i];
  262. bestIndex = i;
  263. for(int k = 0 ; k < bugu[i].ans. length; k + +)
  264. bestans[k] = bugu[i].ans[k];
  265. }
  266. ansCost = bestCost;
  267. / /see.seeBest(bestans, ansCost);
  268. / / if(ansCost > - 1)
  269. / / return;
  270. while(Bugu.step > 0) {
  271. / /Bugu.seeBugu(bugu);
  272. Bugu.step--;
  273. / /找个窝下蛋,更新窝的位置。即莱维飞行。
  274. for(int i = 0 ; i < Bugu.n ; i + +) {
  275. / /System.out.println(updata);
  276. for(int j = 0 ; j < bugu[i].ans. length ; j + +) {
  277. double s = see.getS(),d = see.getD(); / /s是步长,d是方向
  278. double updata = s * d;
  279. bugu[i].ans[j] + = Math.abs(bestIndex - i) *(updata);
  280. / /
  281. }
  282. }
  283. / /在差的解当中, 25%的蛋被宿主发现
  284. for(int i = 0 ; i < Bugu.n ; i + + ) {
  285. if(Math. random() < 0.25) {
  286. for(int j = 0 ; j < bugu[i].ans. length ; j + +) {
  287. double newUpdata = see.randomWalk();
  288. bugu[i].ans[j] + = 0.4 * newUpdata;
  289. }
  290. }
  291. }
  292. / /寻找本轮最佳
  293. bestCost = 0x 3f 3f 3f;
  294. for(int i = 0 ; i < cost. length ; i + +) {
  295. cost[i] = Data.getCost(see.recode(bugu[i].ans));
  296. / /System.out.println( "cost[i]:" + cost[i]);
  297. / /see.seeIndex(bugu[i].ans);
  298. if(cost[i] < bestCost) {
  299. bestCost = cost[i];
  300. bestIndex = i;
  301. }
  302. }
  303. / /本轮最佳比全局最佳好,则更新。
  304. if(bestCost < ansCost) {
  305. ansCost = bestCost;
  306. for(int i = 0 ; i < bestans. length ; i + +)
  307. bestans[i] = bugu[bestIndex].ans[i];
  308. }
  309. / /see.seeBest(bestans, ansCost);
  310. / /Bugu.seeBugu(bugu);
  311. }
  312. / /see.seeBest(bestans, ansCost); / /这一行只是用来打印结果的,按需删除
  313. / /ansCost就是全局最优解的值,bestans就是最优解的序列。
  314. System.out.println( "本次布谷鸟找到的最优解是:" + ansCost);
  315. Data.reVec(see.recode(bestans));
  316. }
  317. }
  318. public class Jsp {
  319. public static void main( String[] argv) {
  320. / / test.myRead();
  321. run. Run();
  322. / / test.print();
  323. }
  324. }
  325. / *
  326. 3
  327. 3
  328. 4
  329. 1000
  330. 3
  331. 0 3
  332. 1 2
  333. 2 2
  334. 3
  335. 0 2
  336. 2 1
  337. 1 4
  338. 2
  339. 1 4
  340. 2 3
  341. * /

 算例测试


   
   
  1. 10
  2. 5
  3. 1000
  4. 20000
  5. 5
  6. 1 21
  7. 0 53
  8. 4 95
  9. 3 55
  10. 2 34
  11. 5
  12. 0 21
  13. 3 52
  14. 4 16
  15. 2 26
  16. 1 71
  17. 5
  18. 3 39
  19. 4 98
  20. 1 42
  21. 2 31
  22. 0 12
  23. 5
  24. 1 77
  25. 0 55
  26. 4 79
  27. 2 66
  28. 3 77
  29. 5
  30. 0 83
  31. 3 34
  32. 2 64
  33. 1 19
  34. 4 37
  35. 5
  36. 1 54
  37. 2 43
  38. 4 79
  39. 0 92
  40. 3 62
  41. 5
  42. 3 69
  43. 4 77
  44. 1 87
  45. 2 87
  46. 0 93
  47. 5
  48. 2 38
  49. 0 60
  50. 1 41
  51. 3 24
  52. 4 83
  53. 5
  54. 3 17
  55. 1 49
  56. 4 25
  57. 0 44
  58. 2 98
  59. 5
  60. 4 77
  61. 3 79
  62. 2 43
  63. 1 75
  64. 0 96

计算结果

 实际上,在种群数量为5,迭代次数为20000次时,已经可以得到总时间为711的最优解,而在上述例子将迭代次数与种群数量大大提高的情况下,结果如下:


   
   
  1. 本次布谷鸟找到的最优解是: 690
  2. machine 0:
  3. task : 4 start: 0 end: 83
  4. task : 0 start: 83 end: 136
  5. task : 7 start: 156 end: 216
  6. task : 1 start: 216 end: 237
  7. task : 3 start: 237 end: 292
  8. task : 5 start: 292 end: 384
  9. task : 6 start: 390 end: 483
  10. task : 8 start: 483 end: 527
  11. task : 9 start: 527 end: 623
  12. task : 2 start: 656 end: 668
  13. machine 1:
  14. task : 0 start: 0 end: 21
  15. task : 5 start: 21 end: 75
  16. task : 3 start: 75 end: 152
  17. task : 6 start: 154 end: 241
  18. task : 7 start: 241 end: 282
  19. task : 8 start: 282 end: 331
  20. task : 9 start: 331 end: 406
  21. task : 4 start: 406 end: 425
  22. task : 2 start: 546 end: 588
  23. task : 1 start: 588 end: 659
  24. machine 2:
  25. task : 5 start: 75 end: 118
  26. task : 7 start: 118 end: 156
  27. task : 9 start: 196 end: 239
  28. task : 4 start: 239 end: 303
  29. task : 6 start: 303 end: 390
  30. task : 1 start: 423 end: 449
  31. task : 3 start: 449 end: 515
  32. task : 8 start: 527 end: 625
  33. task : 2 start: 625 end: 656
  34. task : 0 start: 656 end: 690
  35. machine 3:
  36. task : 6 start: 0 end: 69
  37. task : 4 start: 83 end: 117
  38. task : 9 start: 117 end: 196
  39. task : 8 start: 196 end: 213
  40. task : 1 start: 237 end: 289
  41. task : 2 start: 289 end: 328
  42. task : 7 start: 328 end: 352
  43. task : 0 start: 352 end: 407
  44. task : 3 start: 515 end: 592
  45. task : 5 start: 592 end: 654
  46. machine 4:
  47. task : 9 start: 0 end: 77
  48. task : 6 start: 77 end: 154
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值