深圳幻海软件技术有限公司 欢迎您!

多目标优化NSGA-II的实现(MATLAB完整代码)

2023-06-29

由于历史原因,没有整理好完整的代码,所以在【多目标优化NSGA-II的实现和测试(MATLAB实现)】中只放了部分代码。现在已经整理好了代码,此部分的代码测试内容为:ZDT1、ZDT2、ZDT3、ZDT4、ZDT6。目录主要内容代码模块其他内容运行注意事项 代码nsga2_testnsga

由于历史原因,没有整理好完整的代码,所以在【多目标优化NSGA-II的实现和测试(MATLAB实现)】中只放了部分代码。

现在已经整理好了代码,此部分的代码测试内容为:ZDT1、ZDT2、ZDT3、ZDT4、ZDT6。


目录

主要内容

代码模块

其他内容

运行注意事项

 代码

nsga2_test

nsga2_main

get_variable_bounds

init_pop

sort_pop

select_parent

myga

combined_pop

select_pop

calculate_gd

calculate_sp

calculate_pop

plotPareto

运行结果



主要内容

代码模块

  • nsga2_test:测试函数,用于保存测试数据
  • nsga2_main:主函数,,用于运行NSGA2算法的框架
  • get_variable_bounds:获取种群范围
  • init_pop:种群初始化
  • sort_pop:种群排序
  • select_parent:选择父代
  • myga:进行遗传算法,杂交变异
  • combined_pop:子代和原始种群进行合并
  • select_pop:选择新一代种群
  • calculate_gd:计算GD
  • calculate_sp:计算SP
  • calculate_pop:计算种群

其他内容

plotPareto:画出已知前言数据,用于跟测试得到的前言的可视化对比

 如果需要获取已知的ZDT1、ZDT2、ZDT3、ZDT4、ZDT6的前言数据,通过以下链接获取:

ZDT前沿数据.zip-互联网文档类资源-CSDN文库https://download.csdn.net/download/weixin_44034444/73514580

运行注意事项

  1. 在nsga2_test中设置pop_size,iterations。以及测试次数test。nsga2_test可以保存每一个测试函数,每一测试中每一代的种群数据以及GD和SP的数据。主要是为了方便用获取得到的数据进行分析。
  2. 如果只是需要查看nsga2的效果,运行nsga2_main函数,注意pop_size,iterations的设置。

 代码

nsga2_test

  1. clc
  2. clear
  3. % 定义全局变量,
  4. global pop_size;
  5. global iterations;
  6. pop_size = 100;%种群大小
  7. iterations = 500;%迭代次数
  8. test = 1; %测试次数
  9. for x = 1:5%选择需要计算的函数
  10. switch x
  11. case 1
  12. [~,dim] = get_variable_bounds(x);
  13. %dim = 10;
  14. %设置保存参数
  15. testGD = zeros(test,iterations);
  16. testSP = zeros(test,iterations);
  17. testPop = zeros(test,iterations,pop_size,dim+4);
  18. NSGA2_zdt1 = [];
  19. disp('正在测试zdt1')
  20. for i = 1:test
  21. disp(['第' num2str(i) '次测试']);
  22. [pop,GD,SP] = nsga2_main(x);
  23. testGD(i,:) = GD;
  24. testSP(i,:) = SP;
  25. testPop(i,:,:,:) = pop;
  26. end
  27. NSGA2_zdt1.testGD = testGD;
  28. NSGA2_zdt1.testSP = testSP;
  29. NSGA2_zdt1.testPop = testPop;
  30. save('NSGA2_zdt1.mat','NSGA2_zdt1')
  31. case 2
  32. [~,dim] = get_variable_bounds(x);
  33. %dim = 10;
  34. %设置保存参数
  35. testGD = zeros(test,iterations);
  36. testSP = zeros(test,iterations);
  37. testPop = zeros(test,iterations,pop_size,dim+4);
  38. NSGA2_zdt2 = [];
  39. disp('正在测试zdt2')
  40. for i = 1:test
  41. disp(['第' num2str(i) '次测试']);
  42. [pop,GD,SP] = nsga2_main(x);
  43. testGD(i,:) = GD;
  44. testSP(i,:) = SP;
  45. testPop(i,:,:,:) = pop;
  46. end
  47. NSGA2_zdt2.testGD = testGD;
  48. NSGA2_zdt2.testSP = testSP;
  49. NSGA2_zdt2.testPop = testPop;
  50. save('NSGA2_zdt2.mat','NSGA2_zdt2')
  51. case 3
  52. [~,dim] = get_variable_bounds(x);
  53. %dim = 10;
  54. %设置保存参数
  55. testGD = zeros(test,iterations);
  56. testSP = zeros(test,iterations);
  57. testPop = zeros(test,iterations,pop_size,dim+4);
  58. NSGA2_zdt3 = [];
  59. disp('正在测试zdt3')
  60. for i = 1:test
  61. disp(['第' num2str(i) '次测试']);
  62. [pop,GD,SP] = nsga2_main(x);
  63. testGD(i,:) = GD;
  64. testSP(i,:) = SP;
  65. testPop(i,:,:,:) = pop;
  66. end
  67. NSGA2_zdt3.testGD = testGD;
  68. NSGA2_zdt3.testSP = testSP;
  69. NSGA2_zdt3.testPop = testPop;
  70. save('NSGA2_zdt3.mat','NSGA2_zdt3')
  71. case 4
  72. [~,dim] = get_variable_bounds(x);
  73. %dim = 10;
  74. %设置保存参数
  75. testGD = zeros(test,iterations);
  76. testSP = zeros(test,iterations);
  77. testPop = zeros(test,iterations,pop_size,dim+4);
  78. NSGA2_zdt4 = [];
  79. disp('正在测试zdt4')
  80. for i = 1:test
  81. disp(['第' num2str(i) '次测试']);
  82. [pop,GD,SP] = nsga2_main(x);
  83. testGD(i,:) = GD;
  84. testSP(i,:) = SP;
  85. testPop(i,:,:,:) = pop;
  86. end
  87. NSGA2_zdt4.testGD = testGD;
  88. NSGA2_zdt4.testSP = testSP;
  89. NSGA2_zdt4.testPop = testPop;
  90. save('NSGA2_zdt4.mat','NSGA2_zdt4')
  91. case 5
  92. [~,dim] = get_variable_bounds(x);
  93. %dim = 10;
  94. %设置保存参数
  95. testGD = zeros(test,iterations);
  96. testSP = zeros(test,iterations);
  97. testPop = zeros(test,iterations,pop_size,dim+4);
  98. NSGA2_zdt6 = [];
  99. disp('正在测试zdt6')
  100. for i = 1:test
  101. disp(['第' num2str(i) '次测试']);
  102. [pop,GD,SP] = nsga2_main(x);
  103. testGD(i,:) = GD;
  104. testSP(i,:) = SP;
  105. testPop(i,:,:,:) = pop;
  106. end
  107. NSGA2_zdt6.testGD = testGD;
  108. NSGA2_zdt6.testSP = testSP;
  109. NSGA2_zdt6.testPop = testPop;
  110. save('NSGA2_zdt6.mat','NSGA2_zdt6')
  111. end
  112. end

nsga2_main

  1. function [allpop,GD,SP] = nsga2_main(x)
  2. % 测试主函数 x,问题编号
  3. % 输出种群,GD和SP
  4. % 参数设置
  5. global pop_size
  6. global iterations;%迭代次数
  7. target = 2;
  8. % 获取种群范围
  9. [bounds,dimension] = get_variable_bounds(x);
  10. %种群初始化
  11. pop = init_pop(pop_size,dimension,bounds,x);
  12. %种群排序
  13. pop = sort_pop(pop,target,dimension);
  14. %锦标赛参数设置
  15. parent_size = pop_size/2;
  16. select_size = 2;
  17. % 初始化函数返回数据。
  18. % nsga2_test.m 中需要保存的数据。 如果不跑nsga2_test.m。
  19. GD = zeros(1,iterations);
  20. SP = zeros(1,iterations);
  21. allpop = zeros(iterations,pop_size,dimension+4);%保存进化过程中种群的数据
  22. warning off all
  23. %迭代循环
  24. for i = 1:iterations
  25. %选择父代
  26. parent_pop = select_parent(pop,parent_size,select_size);
  27. %进行遗传算法,杂交变异
  28. child_pop = myga(parent_pop,dimension,bounds,x);
  29. %子代和原始种群进行合并
  30. pop = combined_pop(pop,child_pop,target,dimension);
  31. %对合并种群进行非支配排序
  32. pop = sort_pop(pop,target,dimension);
  33. %选择新一代种群
  34. pop = select_pop(pop,target,dimension,pop_size);
  35. % %画出种群迭代的过程。只运行naga2_main的的时候,可以画出单个测试函数的变化
  36. % plot(pop(:,dimension+1),pop(:,dimension+2),'*')
  37. % grid on
  38. % title(['NSGA2测试第',num2str(x),'个函数第 ',num2str(i),' 代结果'])
  39. % pause(0.1)
  40. %保存数据,计算每一代的GD和SP,也可以通过保存allpop后单独计算
  41. allpop(i,:,:) = pop;
  42. GD(1,i) = calculate_gd(pop,x);
  43. SP(1,i) = calculate_sp(pop);
  44. end
  45. end

get_variable_bounds

  1. function [bounds,dimension] = get_variable_bounds(x)
  2. switch x
  3. case 1
  4. dimension = 30;
  5. bounds = [ones(dimension,1)*0,ones(dimension,1)*1];
  6. case 2
  7. dimension = 30;
  8. bounds = [ones(dimension,1)*0,ones(dimension,1)*1];
  9. case 3
  10. dimension = 30;
  11. bounds = [ones(dimension,1)*0,ones(dimension,1)*1];
  12. case 4
  13. dimension = 10;
  14. bounds = [zeros(1,1),ones(1,1);ones(9,1).*-5,ones(9,1).*5];
  15. case 5
  16. dimension = 10;
  17. bounds = [ones(dimension,1)*0,ones(dimension,1)*1];
  18. end

init_pop

  1. function pop = init_pop(pop_size,dimension,bounds,x)
  2. p = rand(pop_size,dimension);%生成popsize*dimension的0-1矩阵
  3. %生成定义域范围内种群
  4. for i = 1:dimension
  5. p(:,i) = bounds(i,1)+p(:,i)*(bounds(i,2)-bounds(i,1));
  6. end
  7. %计算种群的适应值
  8. evaluate = calculate_pop(p,x);
  9. pop = [p,evaluate];

sort_pop

  1. function pop = sort_pop(pop_eva,target,dimension)
  2. [N, ~] = size(pop_eva);
  3. front = 1;
  4. F(front).f = [];
  5. individual = [];
  6. %先确定等级为1的个体以及被支配的集合
  7. for i = 1:N
  8. individual(i).n = 0; %支配i的个体个数
  9. individual(i).p = [];%被个体i支配的个体集合
  10. for j = 1:N
  11. less = 0; %判断i是否可以支配j
  12. equal = 0; %判断i是否等于j,序号相同时相等
  13. more = 0; %判断i是否被j支配
  14. for k = 1:target %在每一个目标函数中判断支配关系
  15. if pop_eva(i,dimension+k) < pop_eva(j,dimension+k)
  16. less = less+1;
  17. elseif pop_eva(i,dimension+k) == pop_eva(j,dimension+k)
  18. equal = equal+1;
  19. else
  20. more = more + 1;
  21. end
  22. end
  23. if less == 0 && equal ~= target
  24. individual(i).n = individual(i).n + 1;
  25. elseif more == 0 && equal ~= target
  26. individual(i).p = [individual(i).p j];
  27. end
  28. end
  29. if individual(i).n == 0
  30. pop_eva(i,target+dimension+1) = 1;
  31. F(front).f = [F(front).f i];
  32. end
  33. end
  34. %对对所有种群所有个体进行等级划分
  35. while ~isempty(F(front).f)
  36. Q = [];
  37. for i = 1:length(F(front).f) %等级为1的长度
  38. if ~isempty(individual(F(front).f(i)).p) %等级为1的个体中查找其所支配的个体
  39. for j = 1:length((individual(F(front).f(i)).p))%当前个体等级为1的个体所支配的个体数量
  40. individual(individual(F(front).f(i)).p(j)).n = ...
  41. individual(individual(F(front).f(i)).p(j)).n - 1;
  42. if individual(individual(F(front).f(i)).p(j)).n == 0
  43. pop_eva(individual(F(front).f(i)).p(j),target + dimension + 1) = front + 1;
  44. Q = [Q individual(F(front).f(i)).p(j)]; %记录下一等级的集合
  45. end
  46. end
  47. end
  48. end
  49. front = front + 1;
  50. F(front).f = Q;
  51. end
  52. %排序
  53. [~, index_front] = sort(pop_eva(:,target + dimension +1));%根据等级对个体进行排序
  54. sort_front = zeros(size(pop_eva));
  55. for i = 1 : length(index_front)
  56. sort_front(i,:) = pop_eva(index_front(i),:); %排序后的结果
  57. end
  58. current_index = 0; %当前下标。
  59. %计算拥挤距离
  60. for front = 1 : (length(F)-1)
  61. distance = 0;
  62. y =[];
  63. previous_index = current_index + 1;
  64. for i = 1 : length(F(front).f)
  65. y(i,:) = sort_front(current_index + i,:);
  66. end
  67. current_index = current_index + i;
  68. sorted_based_on_objective = [];
  69. %函数值排序
  70. for i = 1 : target
  71. %函数值排序
  72. [sorted_based_on_objective, index_of_objectives] = sort(y(:,dimension + i));
  73. sorted_based_on_objective = [];
  74. for j = 1 : length(index_of_objectives)
  75. sorted_based_on_objective(j,:) = y(index_of_objectives(j),:);
  76. end
  77. f_max = ...
  78. sorted_based_on_objective(length(index_of_objectives), dimension + i);
  79. f_min = sorted_based_on_objective(1, dimension + i);
  80. y(index_of_objectives(length(index_of_objectives)),target + dimension + 1 + i)...
  81. = Inf;
  82. y(index_of_objectives(1),target + dimension + 1 + i) = Inf;
  83. for j = 2 : length(index_of_objectives) - 1
  84. next_obj = sorted_based_on_objective(j + 1,dimension + i);
  85. previous_obj = sorted_based_on_objective(j - 1,dimension + i);
  86. if (f_max - f_min == 0)
  87. y(index_of_objectives(j),target + dimension + 1 + i) = Inf;
  88. else
  89. y(index_of_objectives(j),target + dimension + 1 + i) = ...
  90. (next_obj - previous_obj)/(f_max - f_min);
  91. end
  92. end
  93. end
  94. distance = [];
  95. distance(:,1) = zeros(length(F(front).f),1);
  96. for i = 1 : target
  97. distance(:,1) = distance(:,1) + y(:,target + dimension + 1 + i);
  98. end
  99. y(:,target + dimension + 2) = distance;
  100. y = y(:,1 : target + dimension + 2);
  101. z(previous_index:current_index,:) = y;
  102. end
  103. pop = z();

select_parent

  1. function parent_pop = select_parent(pop,parent_size,compare_size)
  2. %父代个体的选择
  3. [pop_size,distance] = size(pop);
  4. rank = distance-1; %记录等级所在的列
  5. select_pop = zeros(compare_size,distance);
  6. for i = 1:parent_size
  7. %生成参与锦标赛的个体序列
  8. parent_list = randperm(pop_size,compare_size);
  9. %参与锦标赛的个体集合
  10. for j = 1:compare_size
  11. select_pop(j,:) = pop(parent_list(j),:);
  12. end
  13. [min_rank,min_rank_index] = min(select_pop(:,rank));
  14. if length(min_rank)==1
  15. parent_pop(i,:) = select_pop(min_rank_index,:);
  16. else
  17. %最小等级相同的个体集合
  18. for k = 1:length(min_rank)
  19. select_pop1(k,:) = select_pop(min_rank_index(k),:);
  20. end
  21. [~,max_distance_index] = max(select_pop1(:,distance));
  22. parent_pop(i,:) = select_pop1(max_distance_index(1),:);
  23. end
  24. end

myga

  1. function child_pop = myga(parent_pop,dimension,bounds,x)
  2. %GA算法
  3. parent_pop = sortrows(parent_pop,[2+dimension+1,-(2+dimension+2)]);
  4. parent_pop = parent_pop(:,1:dimension);
  5. [popsize,~] = size(parent_pop);
  6. %定义交叉变异的概率
  7. crossover = 1;
  8. mutation = 1;
  9. nc=20;
  10. child = [];
  11. for i = 1:popsize
  12. c_r = rand(1);
  13. m_r = rand(1);
  14. %交叉变换
  15. if c_r < crossover
  16. %随机选择一个个体与该个体进行杂交
  17. p1 = randperm(popsize,1);
  18. parent1 = parent_pop(p1,:);
  19. parent2 = parent_pop(i,:);
  20. % 多项式杂交
  21. child1 = zeros(1,dimension);
  22. child2 = zeros(1,dimension);
  23. for j = 1:dimension
  24. r = rand(1);
  25. if r <= 0.5
  26. a = (2*r)^(1/(nc+1));
  27. else
  28. a= (2*(1-r))^(-(1/(nc+1)));
  29. end
  30. child1(j) = ((1+a)*parent1(j) + (1-a)*parent2(j))/2;
  31. child2(j) = ((1-a)*parent1(j) + (1+a)*parent2(j))/2;
  32. if child1(j) > bounds(j,2)
  33. child1(j) = bounds(j,2);
  34. elseif child1(j) < bounds(j,1)
  35. child1(j) = bounds(j,1);
  36. end
  37. if child2(j) > bounds(j,2)
  38. child2(j) = bounds(j,2);
  39. elseif child2(j) < bounds(j,1)
  40. child2(j) = bounds(j,1);
  41. end
  42. end
  43. child = [child;child1;child2];
  44. end
  45. if m_r < mutation
  46. child3=parent_pop(i,:);
  47. for k = 1:dimension
  48. r = rand();
  49. if r<0.5
  50. m = (2*r)^(1/21)-1;
  51. else
  52. m = 1 - (2*(1 - r))^(1/(21));
  53. end
  54. child3(1,k) = child3(1,k)+m;
  55. if child3(1,k)>bounds(k,2)
  56. child3(1,k) = bounds(k,2);
  57. end
  58. if child3(1,k)<bounds(k,1)
  59. child3(1,k) = bounds(k,1);
  60. end
  61. end
  62. child = [child;child3];
  63. end
  64. end
  65. child_pop = child(:,1:dimension);
  66. child_eva = calculate_pop(child_pop,x);
  67. child_pop = [child_pop,child_eva];

combined_pop

  1. function pop = combined_pop(pop,child_pop,target,dimension)
  2. %合并父代和子代个体
  3. pop1 = pop(:,1:target+dimension);
  4. clear pop
  5. pop = [pop1;child_pop];

select_pop

  1. function pop = select_pop(pop,target,dimension,pop_size)
  2. [popsize,~] = size(pop);
  3. sort_pop = sortrows(pop,[target+dimension+1,-(target+dimension+2)]);
  4. s_pop = [];
  5. no_index = [];
  6. num = 0;
  7. if popsize > pop_size
  8. %根据等级对pop进行升序排序,对拥挤距离进行降序排序
  9. for i = 1:popsize-1
  10. a = sort_pop(i,dimension+1:dimension+2);
  11. b = sort_pop(i+1,dimension+1:dimension+2);
  12. if norm(a-b)>1e-10
  13. s_pop = [s_pop;sort_pop(i,:)];
  14. num = num+1;
  15. if num == pop_size
  16. break;
  17. end
  18. else
  19. no_index = [no_index;i];
  20. end
  21. end
  22. if size(s_pop,1)< pop_size
  23. n = pop_size - size(s_pop,1);
  24. for j = 1:n
  25. s_pop = [s_pop;sort_pop(no_index(j),:)];
  26. end
  27. end
  28. pop = s_pop;
  29. end

calculate_gd

  1. function GD = calculate_gd(pop,x)
  2. switch x
  3. case 1
  4. y = importdata('前沿数据/ZDT1.txt');
  5. case 2
  6. y = importdata('前沿数据/ZDT2.txt');
  7. case 3
  8. y = importdata('前沿数据/ZDT3.txt');
  9. case 4
  10. zdt4 = importdata('前沿数据/ZDT4.txt');
  11. y = sortrows(zdt4,[1,2]);
  12. case 5
  13. y = importdata('前沿数据/ZDT6.txt');
  14. end
  15. %pop测试结果,y真实值
  16. GD = 0;
  17. [n,d] = size(pop);
  18. pop = pop(:,d-3:d-2);
  19. for i = 1:n
  20. dis = pdist2(pop(i,:),y,'euclidean');
  21. gd = (min(dis))^2;
  22. % gd = min(dis);
  23. GD = GD + gd;
  24. end
  25. GD = sqrt(GD/n);
  26. % GD = GD/n
  27. end

calculate_sp

  1. function SP = calculate_sp(pop)
  2. [x,y] = size(pop);
  3. pop = pop(:,y-3:y-2);
  4. mindis = zeros(x,1);
  5. for i = 1:x
  6. di = pop(i,:);
  7. dis = pdist2(di,pop,'euclidean');
  8. dis = sort(dis);
  9. mindis(i) = dis(2);
  10. end
  11. meandis = mean(mindis);
  12. Sp = 0;
  13. for j = 1:x
  14. sp = (meandis-mindis(j))^2;
  15. Sp = Sp + sp;
  16. end
  17. SP = sqrt(Sp/x)/meandis;

calculate_pop

  1. function evaluate = calculate_pop(pop,x)
  2. %测试函数
  3. [~,dim] = size(pop);
  4. switch x
  5. case 1 %ZDT1
  6. fx1 = pop(:,1);
  7. gx = 1+sum(pop(:,2:end),2).*(9/(dim-1));
  8. hx = 1-sqrt(fx1./gx);
  9. fx2 = gx.*hx;
  10. evaluate = [fx1,fx2];
  11. case 2 %ZDT2
  12. fx1 = pop(:,1);
  13. gx = 1+sum(pop(:,2:end),2).*(9/(dim-1));
  14. hx = 1-(fx1./gx).^2;
  15. fx2 = gx.*hx;
  16. evaluate = [fx1,fx2];
  17. case 3 %ZDT3
  18. fx1 = pop(:,1);
  19. gx = 1+sum(pop(:,2:end),2).*(9/(dim-1));
  20. hx = 1-sqrt(fx1./gx)-(fx1./gx).*sin(10*pi.*fx1);
  21. fx2 = gx.*hx;
  22. evaluate = [fx1,fx2];
  23. case 4 %ZDT4
  24. fx1 = pop(:,1);
  25. gx = 91+sum((pop(:,2:dim).^2-10.*cos(4*pi.*pop(:,2:dim))),2);
  26. hx = 1-sqrt(fx1./gx);
  27. fx2 = gx.*hx;
  28. evaluate = [fx1,fx2];
  29. case 5
  30. x1 = pop(:,1);
  31. fx1 = 1-exp(-4.*x1).*(sin(6*pi.*x1)).^6;
  32. s = sum(pop(:,2:end),2);
  33. gx = 1+9/(dim-1).*s;
  34. hx = 1-(fx1./gx).^2;
  35. fx2 = gx.*hx;
  36. evaluate = [fx1,fx2];
  37. case 6
  38. n = -sum((pop-1/sqrt(dim)).^2,2);
  39. m = -sum((pop+1/sqrt(dim)).^2,2);
  40. fx1 = 1-exp(n);
  41. fx2 = 1-exp(m);
  42. evaluate = [fx1,fx2];
  43. end

plotPareto

  1. function plotPareto(x)
  2. switch x
  3. case 1
  4. zdt1 = importdata('前沿数据/ZDT1.txt');
  5. hold on
  6. plot(zdt1(:,1),zdt1(:,2),'-')
  7. legend('改进NSGA2测试前沿','理想前沿')
  8. case 2
  9. zdt2 = importdata('前沿数据/ZDT2.txt');
  10. hold on
  11. plot(zdt2(:,1),zdt2(:,2),'-')
  12. legend('测试前沿','已知前沿')
  13. case 3
  14. zdt3 = importdata('前沿数据/ZDT3.txt');
  15. hold on
  16. plot(zdt3(:,1),zdt3(:,2),'*')
  17. legend('测试前沿','已知前沿')
  18. case 4
  19. zdt4 = importdata('前沿数据/ZDT4.txt');
  20. zdt4 = sortrows(zdt4,[1,2]);
  21. hold on
  22. plot(zdt4(:,1),zdt4(:,2),'-')
  23. legend('测试前沿','已知前沿')
  24. case 5
  25. zdt6 = importdata('前沿数据/ZDT6.txt');
  26. hold on
  27. plot(zdt6(:,1),zdt6(:,2),'-')
  28. legend('测试前沿','已知前沿')
  29. otherwise
  30. fprintf('错误')
  31. end
  32. end

运行结果

运行过程

保存的数据 

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览48554 人正在系统学习中