基于拥挤距离的多目标粒子群优化算法的完整MATLAB实现。该算法适用于解决多目标优化问题,并在粒子群算法中引入了拥挤距离机制来维护解的多样性和分布性。
matlab
%% 基于拥挤距离的多目标粒子群优化算法 (MOPSO-CD)
% 功能: 解决多目标优化问题,使用拥挤距离维护解集的多样性和分布性
clear; close all; clc;
%% 1. 算法参数设置
problem.nVar = 10; % 决策变量维度
problem.varMin = -5; % 决策变量下界
problem.varMax = 5; % 决策变量上界
problem.nObj = 2; % 目标函数个数 (此处以2目标为例)
params.nPop = 100; % 种群大小
params.nRep = 100; % 外部档案大小
params.maxIt = 100; % 最大迭代次数
params.w = 0.729; % 惯性权重
params.wdamp = 0.99; % 惯性权重衰减系数
params.c1 = 1.49445; % 个体学习因子
params.c2 = 1.49445; % 社会学习因子
params.nGrid = 7; % 网格数目 (用于密度估计)
params.alpha = 0.1; % 膨胀率
params.beta = 2; % 领导者选择压力系数
params.gamma = 2; % 删除压力系数
params.mu = 0.1; % 变异概率
params.sigma = 0.1*(problem.varMax - problem.varMin); % 变异步长
% 测试问题 (ZDT1, ZDT2, ZDT3等)
problem.name = 'ZDT1'; % 测试问题名称
%% 2. 初始化种群
empty_particle.Position = [];
empty_particle.Velocity = [];
empty_particle.Cost = [];
empty_particle.Best.Position = [];
empty_particle.Best.Cost = [];
empty_particle.IsDominated = [];
empty_particle.DominationSet = [];
empty_particle.DominatedCount = [];
empty_particle.GridIndex = [];
empty_particle.GridSubIndex = [];
pop = repmat(empty_particle, params.nPop, 1);
% 初始化粒子位置和速度
for i = 1:params.nPop
% 初始化位置
pop(i).Position = unifrnd(problem.varMin, problem.varMax, [1, problem.nVar]);
% 初始化速度
pop(i).Velocity = zeros(1, problem.nVar);
% 计算目标函数值
pop(i).Cost = ObjectiveFunction(pop(i).Position, problem);
% 初始化个体最优
pop(i).Best.Position = pop(i).Position;
pop(i).Best.Cost = pop(i).Cost;
end
%% 3. 初始化外部档案 (非支配解集)
rep = DetermineDomination(pop);
rep = pop(~rep);
%% 4. 初始化网格 (用于密度估计)
Grid = CreateGrid(rep, params.nGrid, params.alpha);
% 为外部档案中的解分配网格索引
for i = 1:numel(rep)
rep(i) = FindGridIndex(rep(i), Grid);
end
% 为种群中的解分配网格索引
for i = 1:numel(pop)
pop(i) = FindGridIndex(pop(i), Grid);
end
%% 5. 主循环
best_costs = zeros(params.maxIt, problem.nObj);
archive_sizes = zeros(params.maxIt, 1);
figure('Position', [100, 100, 1200, 800]);
for it = 1:params.maxIt
fprintf('迭代 %d/%d, 外部档案大小: %d\n', it, params.maxIt, numel(rep));
% 5.1 为每个粒子选择领导者 (基于拥挤距离和网格密度)
leader = SelectLeader(rep, params.beta);
% 5.2 更新粒子速度和位置
for i = 1:params.nPop
% 选择领导者
if isempty(leader)
leader_selected = pop(i).Best;
else
leader_selected = leader;
end
% 更新速度
pop(i).Velocity = params.w * pop(i).Velocity ...
+ params.c1 * rand(1, problem.nVar) .* (pop(i).Best.Position - pop(i).Position) ...
+ params.c2 * rand(1, problem.nVar) .* (leader_selected.Position - pop(i).Position);
% 限制速度
vmax = 0.2 * (problem.varMax - problem.varMin);
pop(i).Velocity = max(min(pop(i).Velocity, vmax), -vmax);
% 更新位置
pop(i).Position = pop(i).Position + pop(i).Velocity;
% 边界处理
pop(i).Position = max(pop(i).Position, problem.varMin);
pop(i).Position = min(pop(i).Position, problem.varMax);
% 变异操作 (增加多样性)
if rand < params.mu
pop(i).Position = pop(i).Position + params.sigma * randn(1, problem.nVar);
pop(i).Position = max(pop(i).Position, problem.varMin);
pop(i).Position = min(pop(i).Position, problem.varMax);
end
% 计算新目标值
pop(i).Cost = ObjectiveFunction(pop(i).Position, problem);
% 更新个体最优
if Dominates(pop(i).Cost, pop(i).Best.Cost)
pop(i).Best.Position = pop(i).Position;
pop(i).Best.Cost = pop(i).Cost;
elseif ~Dominates(pop(i).Best.Cost, pop(i).Cost) && rand < 0.5
pop(i).Best.Position = pop(i).Position;
pop(i).Best.Cost = pop(i).Cost;
end
end
% 5.3 合并种群和外部档案
combined_pop = [pop; rep'];
% 5.4 确定支配关系
is_dominated = DetermineDomination(combined_pop);
% 5.5 提取非支配解
rep_candidates = combined_pop(~is_dominated);
% 5.6 更新外部档案 (基于拥挤距离的选择)
rep = UpdateRepository(rep_candidates, params.nRep);
% 5.7 更新网格 (适应解集的变化)
Grid = CreateGrid(rep, params.nGrid, params.alpha);
% 5.8 为外部档案中的解分配新的网格索引
for i = 1:numel(rep)
rep(i) = FindGridIndex(rep(i), Grid);
end
% 5.9 为种群中的解分配网格索引
for i = 1:numel(pop)
pop(i) = FindGridIndex(pop(i), Grid);
end
% 5.10 更新惯性权重
params.w = params.w * params.wdamp;
% 5.11 记录最佳目标值
if ~isempty(rep)
rep_costs = reshape([rep.Cost], problem.nObj, numel(rep))';
best_costs(it, :) = min(rep_costs);
end
archive_sizes(it) = numel(rep);
% 5.12 可视化 (每10代更新一次)
if mod(it, 10) == 0 || it == params.maxIt
% 绘制Pareto前沿
subplot(2, 3, 1);
if ~isempty(rep)
rep_costs = reshape([rep.Cost], problem.nObj, numel(rep))';
scatter(rep_costs(:, 1), rep_costs(:, 2), 30, 'filled', 'b');
end
hold on;
% 绘制真实Pareto前沿 (对于ZDT问题)
if strcmp(problem.name, 'ZDT1')
pf = LoadPF('ZDT1');
plot(pf(:, 1), pf(:, 2), 'r-', 'LineWidth', 2);
legend('算法解', '真实Pareto前沿', 'Location', 'best');
end
title(sprintf('Pareto前沿 (迭代 %d)', it));
xlabel('f_1'); ylabel('f_2');
grid on; hold off;
% 绘制目标值收敛曲线
subplot(2, 3, 2);
plot(1:it, best_costs(1:it, 1), 'b-', 'LineWidth', 1.5);
hold on;
plot(1:it, best_costs(1:it, 2), 'r-', 'LineWidth', 1.5);
title('目标函数收敛曲线');
xlabel('迭代次数'); ylabel('目标函数值');
legend('f_1', 'f_2', 'Location', 'best');
grid on; hold off;
% 绘制外部档案大小变化
subplot(2, 3, 3);
plot(1:it, archive_sizes(1:it), 'g-', 'LineWidth', 1.5);
title('外部档案大小变化');
xlabel('迭代次数'); ylabel('档案大小');
grid on;
% 绘制解的分布 (决策空间)
subplot(2, 3, 4);
if ~isempty(rep)
rep_positions = reshape([rep.Position], problem.nVar, numel(rep))';
for i = 1:min(problem.nVar, 5)
histogram(rep_positions(:, i), 20, 'FaceAlpha', 0.6);
hold on;
end
title('决策变量分布 (前5维)');
xlabel('变量值'); ylabel('频次');
grid on; hold off;
end
% 绘制拥挤距离分布
subplot(2, 3, 5);
if ~isempty(rep)
% 计算拥挤距离
rep_costs = reshape([rep.Cost], problem.nObj, numel(rep))';
[~, cd] = CalculateCrowdingDistance(rep_costs);
histogram(cd, 20, 'FaceColor', 'm', 'FaceAlpha', 0.7);
title('拥挤距离分布');
xlabel('拥挤距离'); ylabel('频次');
grid on;
end
% 绘制网格密度
subplot(2, 3, 6);
if ~isempty(Grid)
if problem.nObj == 2
% 对于2目标问题,可视化网格密度
grid_density = zeros(params.nGrid, params.nGrid);
for i = 1:numel(rep)
if ~isempty(rep(i).GridIndex) && length(rep(i).GridIndex) == 2
gi = rep(i).GridIndex;
grid_density(gi(1), gi(2)) = grid_density(gi(1), gi(2)) + 1;
end
end
imagesc(grid_density');
colorbar;
title('网格密度分布');
xlabel('网格维度1'); ylabel('网格维度2');
end
end
drawnow;
end
end
%% 6. 最终结果分析
fprintf('\n========== MOPSO-CD 算法结果 ==========\n');
fprintf('迭代次数: %d\n', params.maxIt);
fprintf('种群大小: %d\n', params.nPop);
fprintf('最终外部档案大小: %d\n', numel(rep));
fprintf('问题: %s (决策变量: %d, 目标: %d)\n', problem.name, problem.nVar, problem.nObj);
if ~isempty(rep)
% 计算性能指标
rep_costs = reshape([rep.Cost], problem.nObj, numel(rep))';
% 1. 收敛性指标 (Generational Distance)
if strcmp(problem.name, 'ZDT1')
pf = LoadPF('ZDT1');
gd = CalculateGD(rep_costs, pf);
fprintf('世代距离 (GD): %.6f\n', gd);
% 2. 多样性指标 (Spacing)
spacing = CalculateSpacing(rep_costs);
fprintf('分布性指标 (Spacing): %.6f\n', spacing);
% 3. 超体积指标 (Hypervolume)
ref_point = max(pf) * 1.1; % 参考点
hv = CalculateHypervolume(rep_costs, ref_point);
fprintf('超体积指标 (HV): %.6f\n', hv);
end
% 显示非支配解
fprintf('\n前10个非支配解:\n');
fprintf('%-5s %-12s %-12s\n', '序号', 'f1', 'f2');
for i = 1:min(10, numel(rep))
fprintf('%-5d %-12.6f %-12.6f\n', i, rep_costs(i, 1), rep_costs(i, 2));
end
end
%% 7. 辅助函数定义
% 目标函数 (ZDT测试问题)
function z = ObjectiveFunction(x, problem)
nVar = length(x);
switch problem.name
case 'ZDT1'
% ZDT1问题
f1 = x(1);
g = 1 + 9 * sum(x(2:end)) / (nVar - 1);
h = 1 - sqrt(f1 / g);
f2 = g * h;
z = [f1, f2];
case 'ZDT2'
% ZDT2问题
f1 = x(1);
g = 1 + 9 * sum(x(2:end)) / (nVar - 1);
h = 1 - (f1 / g)^2;
f2 = g * h;
z = [f1, f2];
case 'ZDT3'
% ZDT3问题
f1 = x(1);
g = 1 + 9 * sum(x(2:end)) / (nVar - 1);
h = 1 - sqrt(f1 / g) - (f1 / g) * sin(10 * pi * f1);
f2 = g * h;
z = [f1, f2];
otherwise
% 默认: 多目标Sphere函数
z = zeros(1, problem.nObj);
for i = 1:problem.nObj
z(i) = sum((x - i).^2);
end
end
end
% 判断支配关系
function b = Dominates(x, y)
% x支配y: x在所有目标上都不比y差,且至少在一个目标上比y好
b = all(x <= y) && any(x < y);
end
% 确定种群中每个个体是否被支配
function is_dominated = DetermineDomination(pop)
nPop = numel(pop);
is_dominated = false(nPop, 1);
for i = 1:nPop
for j = 1:nPop
if i ~= j && Dominates(pop(j).Cost, pop(i).Cost)
is_dominated(i) = true;
break;
end
end
end
end
% 创建网格
function Grid = CreateGrid(pop, nGrid, alpha)
if isempty(pop)
Grid.LB = [];
Grid.UB = [];
Grid.W = [];
Grid.nObj = 0;
return;
end
costs = reshape([pop.Cost], length(pop(1).Cost), numel(pop))';
nObj = size(costs, 2);
% 计算每个目标的最小值和最大值
cmin = min(costs, [], 1);
cmax = max(costs, [], 1);
% 添加膨胀系数
dc = cmax - cmin;
cmin = cmin - alpha * dc;
cmax = cmax + alpha * dc;
% 初始化网格
Grid.LB = cmin;
Grid.UB = cmax;
Grid.nObj = nObj;
Grid.nGrid = nGrid;
% 计算网格宽度
Grid.W = (cmax - cmin) / nGrid;
end
% 为个体分配网格索引
function particle = FindGridIndex(particle, Grid)
if isempty(Grid.LB)
particle.GridIndex = [];
particle.GridSubIndex = [];
return;
end
nObj = Grid.nObj;
GridSubIndex = zeros(1, nObj);
for j = 1:nObj
GridSubIndex(j) = floor((particle.Cost(j) - Grid.LB(j)) / Grid.W(j)) + 1;
GridSubIndex(j) = max(1, min(GridSubIndex(j), Grid.nGrid));
end
particle.GridSubIndex = GridSubIndex;
% 计算线性网格索引
nGrid = Grid.nGrid;
particle.GridIndex = GridSubIndex(1);
for j = 2:nObj
particle.GridIndex = particle.GridIndex + (GridSubIndex(j) - 1) * nGrid^(j-1);
end
end
% 选择领导者 (基于拥挤距离和网格密度)
function leader = SelectLeader(rep, beta)
if isempty(rep)
leader = [];
return;
end
% 计算拥挤距离
costs = reshape([rep.Cost], length(rep(1).Cost), numel(rep))';
[~, crowding_distances] = CalculateCrowdingDistance(costs);
% 计算选择概率 (基于拥挤距离)
crowding_distances = max(crowding_distances, 0);
if sum(crowding_distances) == 0
% 如果所有拥挤距离为0,均匀选择
probabilities = ones(size(crowding_distances)) / numel(crowding_distances);
else
% 基于拥挤距离的指数选择
probabilities = (crowding_distances .^ beta) / sum(crowding_distances .^ beta);
end
% 轮盘赌选择
r = rand;
c = cumsum(probabilities);
leader_idx = find(r <= c, 1, 'first');
if isempty(leader_idx)
leader_idx = randi(numel(rep));
end
leader = rep(leader_idx).Best;
end
% 计算拥挤距离
function [sorted_costs, crowding_distances] = CalculateCrowdingDistance(costs)
n = size(costs, 1);
m = size(costs, 2);
crowding_distances = zeros(n, 1);
if n <= 2
crowding_distances(:) = Inf;
sorted_costs = costs;
return;
end
sorted_costs = costs;
for i = 1:m
% 对第i个目标排序
[sorted_costs_i, order_i] = sort(costs(:, i));
% 边界点的拥挤距离设为无穷大
crowding_distances(order_i(1)) = Inf;
crowding_distances(order_i(end)) = Inf;
% 计算中间点的拥挤距离
fmin = sorted_costs_i(1);
fmax = sorted_costs_i(end);
if fmax - fmin > 0
for j = 2:n-1
crowding_distances(order_i(j)) = crowding_distances(order_i(j)) ...
+ (sorted_costs_i(j+1) - sorted_costs_i(j-1)) / (fmax - fmin);
end
end
end
end
% 更新外部档案 (基于拥挤距离的截断)
function new_rep = UpdateRepository(rep_candidates, nRep)
if numel(rep_candidates) <= nRep
new_rep = rep_candidates;
return;
end
% 计算拥挤距离
costs = reshape([rep_candidates.Cost], length(rep_candidates(1).Cost), numel(rep_candidates))';
[~, crowding_distances] = CalculateCrowdingDistance(costs);
% 根据拥挤距离排序
[~, sorted_indices] = sort(crowding_distances, 'descend');
% 选择拥挤距离最大的前nRep个解
selected_indices = sorted_indices(1:nRep);
new_rep = rep_candidates(selected_indices);
end
% 加载真实Pareto前沿 (对于ZDT问题)
function pf = LoadPF(problem_name)
switch problem_name
case 'ZDT1'
% ZDT1的Pareto前沿
f1 = linspace(0, 1, 100)';
f2 = 1 - sqrt(f1);
pf = [f1, f2];
case 'ZDT2'
% ZDT2的Pareto前沿
f1 = linspace(0, 1, 100)';
f2 = 1 - f1.^2;
pf = [f1, f2];
case 'ZDT3'
% ZDT3的Pareto前沿
f1 = [linspace(0, 0.0830015349, 50), ...
linspace(0.1822287280, 0.2577623634, 50), ...
linspace(0.4093136748, 0.4538821041, 50), ...
linspace(0.6183967944, 0.6525117038, 50), ...
linspace(0.8233317983, 0.8518328654, 50)]';
f2 = 1 - sqrt(f1) - f1 .* sin(10 * pi * f1);
pf = [f1, f2];
otherwise
pf = [];
end
end
% 计算世代距离 (GD)
function gd = CalculateGD(A, PF)
% A: 算法得到的Pareto前沿
% PF: 真实的Pareto前沿
nA = size(A, 1);
gd = 0;
for i = 1:nA
% 计算算法解到真实前沿的最小距离
distances = sqrt(sum((PF - A(i, :)).^2, 2));
gd = gd + min(distances);
end
gd = gd / nA;
end
% 计算分布性指标 (Spacing)
function s = CalculateSpacing(A)
n = size(A, 1);
if n <= 2
s = 0;
return;
end
d = zeros(n, 1);
for i = 1:n
% 计算解i到其他解的最小距离
distances = sqrt(sum((A - A(i, :)).^2, 2));
distances(i) = Inf; % 排除自身
d(i) = min(distances);
end
d_bar = mean(d);
s = sqrt(sum((d - d_bar).^2) / (n - 1));
end
% 计算超体积指标 (HV)
function hv = CalculateHypervolume(A, ref_point)
n = size(A, 1);
m = size(A, 2);
if n == 0
hv = 0;
return;
end
% 对解进行排序
[~, indices] = sortrows(A, 1);
A = A(indices, :);
% 递归计算超体积
hv = HypervolumeRecursive(A, ref_point, m);
end
function hv = HypervolumeRecursive(A, ref_point, m)
n = size(A, 1);
if n == 0
hv = 0;
elseif m == 1
% 一维情况
hv = max(0, ref_point(1) - min(A(:, 1)));
elseif m == 2
% 二维情况
hv = 0;
for i = 1:n
if i == 1
width = ref_point(1) - A(i, 1);
else
width = A(i-1, 1) - A(i, 1);
end
height = ref_point(2) - A(i, 2);
hv = hv + width * height;
end
else
% 多维情况 (使用递归)
hv = 0;
for i = 1:n
if i == 1
length_i = ref_point(1) - A(i, 1);
else
length_i = A(i-1, 1) - A(i, 1);
end
if length_i > 0
% 创建投影
A_proj = zeros(i-1, m-1);
for j = 1:i-1
for k = 2:m
A_proj(j, k-1) = max(A(j, k), A(i, k));
end
end
% 递归计算
hv_i = length_i * HypervolumeRecursive(A_proj, ref_point(2:end), m-1);
hv = hv + hv_i;
end
end
end
end
算法核心原理
1. 拥挤距离 (Crowding Distance)
- 用于衡量解在目标空间的分布密度
- 计算方法:对于每个目标,按目标值排序,计算相邻解之间的距离
- 边界解的拥挤距离设为无穷大,以确保它们被保留
2. 网格机制 (Grid)
- 将目标空间划分为多个网格
- 通过网格密度估计解的分布情况
- 维护解的多样性,避免过早收敛
3. 领导者选择策略
- 基于拥挤距离的概率选择
- 拥挤距离大的解有更高概率被选为领导者
- 平衡算法的收敛性和多样性
4. 外部档案维护
- 存储非支配解
- 使用拥挤距离进行截断,保持档案大小恒定
- 确保Pareto前沿的分布均匀性
参考代码 基于拥挤距离的多目标粒子群优化算法 www.3dddown.com/csa/98152.html
关键特性
- 多目标处理能力:可处理2个或更多目标的优化问题
- 多样性维护:通过拥挤距离和网格机制保持解集多样性
- 自适应参数:惯性权重衰减,平衡探索与开发
- 变异操作:引入随机变异,增强全局搜索能力
- 性能评估:提供GD、Spacing、HV等多个性能指标
使用方法
- 修改问题参数 :调整
problem结构体中的参数 - 选择测试问题 :修改
problem.name选择不同的测试函数 - 调整算法参数:根据问题复杂度调整种群大小、迭代次数等
- 运行算法:直接运行MATLAB代码
- 分析结果:观察收敛曲线、Pareto前沿和性能指标
扩展建议
- 添加更多测试问题:如DTLZ系列、WFG系列等
- 改进领导者选择:引入ε支配、模糊支配等机制
- 并行化实现:利用MATLAB并行计算工具箱加速
- 动态参数调整:自适应调整学习因子和变异概率
- 约束处理:扩展算法以处理约束优化问题