基于拥挤距离的多目标粒子群优化算法 (MOPSO-CD)

基于拥挤距离的多目标粒子群优化算法的完整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

关键特性

  1. 多目标处理能力:可处理2个或更多目标的优化问题
  2. 多样性维护:通过拥挤距离和网格机制保持解集多样性
  3. 自适应参数:惯性权重衰减,平衡探索与开发
  4. 变异操作:引入随机变异,增强全局搜索能力
  5. 性能评估:提供GD、Spacing、HV等多个性能指标

使用方法

  1. 修改问题参数 :调整problem结构体中的参数
  2. 选择测试问题 :修改problem.name选择不同的测试函数
  3. 调整算法参数:根据问题复杂度调整种群大小、迭代次数等
  4. 运行算法:直接运行MATLAB代码
  5. 分析结果:观察收敛曲线、Pareto前沿和性能指标

扩展建议

  1. 添加更多测试问题:如DTLZ系列、WFG系列等
  2. 改进领导者选择:引入ε支配、模糊支配等机制
  3. 并行化实现:利用MATLAB并行计算工具箱加速
  4. 动态参数调整:自适应调整学习因子和变异概率
  5. 约束处理:扩展算法以处理约束优化问题
相关推荐
爱吃rabbit的mq12 分钟前
第09章:随机森林:集成学习的威力
算法·随机森林·集成学习
(❁´◡`❁)Jimmy(❁´◡`❁)1 小时前
Exgcd 学习笔记
笔记·学习·算法
YYuCChi1 小时前
代码随想录算法训练营第三十七天 | 52.携带研究材料(卡码网)、518.零钱兑换||、377.组合总和IV、57.爬楼梯(卡码网)
算法·动态规划
不能隔夜的咖喱2 小时前
牛客网刷题(2)
java·开发语言·算法
VT.馒头2 小时前
【力扣】2721. 并行执行异步函数
前端·javascript·算法·leetcode·typescript
进击的小头2 小时前
实战案例:51单片机低功耗场景下的简易滤波实现
c语言·单片机·算法·51单片机
咖丨喱3 小时前
IP校验和算法解析与实现
网络·tcp/ip·算法
罗湖老棍子4 小时前
括号配对(信息学奥赛一本通- P1572)
算法·动态规划·区间dp·字符串匹配·区间动态规划
fengfuyao9854 小时前
基于MATLAB的表面织构油润滑轴承故障频率提取(改进VMD算法)
人工智能·算法·matlab
机器学习之心4 小时前
基于随机森林模型的轴承剩余寿命预测MATLAB实现!
算法·随机森林·matlab