动态规划与TOPSIS熵权法:多阶段多目标规划问题的建模与求解实战

📅 2026/8/22 5:01:41
动态规划与TOPSIS熵权法:多阶段多目标规划问题的建模与求解实战
1. 项目概述从赛题到方案的实战拆解去年带队参加华数杯A题“雅鲁藏布江综合开发规划”让不少队伍头疼。这题表面看是资源规划内核却是一个典型的“多目标、多阶段、强约束”的复杂系统优化问题。你不能只算经济账还得兼顾生态、社会甚至地缘上的微妙平衡。题目给了流域的水文、地理、社会经济数据要求你提出一个未来30年的梯级水电站开发序列规划。说白了就是在时间、空间和多重目标之间找到一个最优的“施工时间表”。这活儿干下来核心就三件事建模、求解、评价。建模是把现实问题翻译成数学语言求解是动用算法把这个数学问题算出来评价则是用一套标准告诉你哪个方案更好。我们当时用的技术栈很明确MATLAB作为计算核心构建了动态规划模型来决策电站建设时序用TOPSIS结合熵权法进行多方案比选。这套组合拳打下来思路清晰结果也经得起推敲。今天我就把这套从赛题到代码的全过程拆解一遍无论是为了备战数模还是学习如何用数学模型解决复杂的现实规划问题相信都能给你带来直接的启发。2. 核心思路与模型架构设计面对雅鲁藏布江开发这种大课题最忌讳一上来就埋头写代码。我们的第一步是问题解构。题目本质是在已知多个潜在坝址每个坝址有建设成本、发电能力、淹没损失、生态影响等参数和未来30年电力需求增长、投资预算等约束下决定每年开工哪个或哪几个电站使得总体的“综合效益”最大。这里的“综合效益”是个复合指标至少包含四个方面经济效益总发电收益减去总建设与运维成本。社会效益如提供的就业岗位、对区域经济发展的带动作用。生态效益对河流连续性、生物多样性、局部气候的负面影响要尽可能小。工程风险涉及地质稳定性、建设难度等。直接用一个公式把四个维度揉在一起是行不通的因为量纲和意义都不同。因此我们采用了经典的两阶段策略第一阶段多目标生成。利用优化模型我们选了动态规划以经济效益最大化为首要目标因其最容易量化同时将生态、社会指标转化为约束条件例如规定累积淹没面积不能超过某个阈值每年必须提供一定数量的就业生成若干个帕累托前沿上的候选建设序列方案。每个方案都是一个具体的电站开工时间表。第二阶段多属性决策。对生成的多个候选方案使用TOPSIS法进行综合评价。这时经济效益、社会效益、生态效益、工程风险都作为评价属性利用熵权法根据各方案数据本身的离散程度客观赋权最终选出“最接近理想解”的最优方案。这个架构的优势在于它将复杂的多目标优化问题进行了分解和序贯处理。动态规划负责在硬约束下搜索可行且经济上较优的路径TOPSIS则负责在多个“较优”路径中平衡各种软性指标做出最终抉择。模型框架如下图所示此处为逻辑描述规划问题输入坝址参数、需求预测、约束条件 →动态规划模型求解生成N个候选开发序列 →方案评价指标体系经济、社会、生态、风险四大类指标 →熵权法确定权重→TOPSIS综合评价排序→输出最优开发规划序列。2.1 为什么选择动态规划在众多优化算法中如线性规划、整数规划、遗传算法选择动态规划Dynamic Programming, DP作为核心求解器是基于本题的几个关键特征多阶段决策30年的规划期自然地划分为30个阶段每年为一个阶段。每个阶段需要做出的决策是“今年开工哪个些电站”。无后效性一旦某个电站在某年开工并投入运营它后续产生的收益和影响只与它的状态是否已建有关而与它是哪一年建的无关。这符合DP的“无后效性”原则。状态空间可管理虽然坝址多但我们可以将“状态”定义为“到第t年为止已建成电站的集合”。通过合理的状态编码如二进制编码状态数量是2^NN为备选电站数。对于N10~15的中等规模问题在计算机上是可解的。对于更大规模可以结合启发式规则进行状态剪枝。精确求解与遗传算法等启发式算法相比DP在状态空间可控时能保证找到全局最优解在给定的目标函数下。这对于竞赛中需要严谨论证的方案来说是一个重要优势。我们的动态规划模型定义如下阶段t 1, 2, ..., 30 (年)。状态S_t 一个N维二进制向量表示第t年初决策前各个电站的建设状态0未建1已建。决策x_t 表示在第t年决定新开工的电站集合是S_t补集的一个子集。状态转移S_{t1} S_t ∪ x_t。即今年开工的电站明年年初变为已建状态。阶段效益R(S_t, x_t) 表示在第t年处于状态S_t并做出决策x_t所带来的净效益。这包括当年新开工电站x_t带来的建设期投资负效益、已建成电站S_t在本年产生的发电收益、以及可能产生的生态与社会影响折算的代价或收益。指标函数V_t(S_t) max_{x_t} { R(S_t, x_t) V_{t1}(S_{t1}) }。 表示从第t年初状态S_t出发采用最优策略到规划期结束所能获得的最大总效益。边界条件V_{31}(S_{31}) 0。规划期结束后不再有效益。求解过程是从第30年逆向递推到第1年最终得到V_1(初始状态)以及每一阶段的最优决策x_t^*从而构成最优开发序列。2.2 TOPSIS与熵权法如何科学地比选方案动态规划可能会生成几十个甚至上百个在经济效益上相差不大的方案。如何从中选出一个“综合最优”的这就需要多属性决策分析。TOPSISTechnique for Order Preference by Similarity to Ideal Solution即“逼近理想解排序法”它的思想非常直观找出正理想解所有方案中各项指标的最优值和负理想解各项指标的最差值然后计算每个方案与这两个解的距离。离正理想解越近、离负理想解越远的方案综合表现越好。其核心步骤如下构建决策矩阵假设有m个方案n个评价指标。形成一个m×n的矩阵X。数据标准化由于指标量纲不同如发电量是亿千瓦时投资是亿元需要消除量纲影响。常用向量归一化z_{ij} x_{ij} / sqrt( sum_{i1}^{m} x_{ij}^2 )。确定权重这是关键一步。我们采用熵权法一种客观赋权法。其原理是某个指标的数据离散程度越大说明该指标在不同方案间的区分能力越强其提供的信息量越大权重也应越高。计算第j项指标下第i个方案的比重p_{ij} z_{ij} / sum_{i1}^{m} z_{ij}。计算第j项指标的熵值e_j -k * sum_{i1}^{m} (p_{ij} * ln(p_{ij})) 其中k1/ln(m) 0。计算差异系数g_j 1 - e_j。熵值越小差异系数越大指标越重要。确定权重w_j g_j / sum_{j1}^{n} g_j。计算加权标准化矩阵V [w_j * z_{ij}]。确定正负理想解正理想解 V ( max(v_{1j}), max(v_{2j}), ..., max(v_{nj}) ) 对于效益型指标取max成本型指标取min。负理想解 V- ( min(v_{1j}), min(v_{2j}), ..., min(v_{nj}) ) 对于效益型指标取min成本型指标取max。计算距离各方案到正理想解的距离S_i sqrt( sum_{j1}^{n} (v_{ij} - v_j)^2 )。各方案到负理想解的距离S_i- sqrt( sum_{j1}^{n} (v_{ij} - v_j-)^2 )。计算相对贴近度C_i S_i- / (S_i S_i-)。排序按C_i值从大到小排序C_i值越大方案越优。熵权法的好处是完全基于数据本身避免了主观打分带来的偏差在数模竞赛中显得非常客观、科学。3. 关键实现细节与MATLAB编程要点思路清晰后实现就是体力活加细心活了。我们整个项目代码在MATLAB中完成主要分为三个模块数据预处理模块、动态规划求解模块、TOPSIS评价模块。3.1 数据预处理一切模型的基础题目提供的数据通常需要清洗和转换。我们遇到了几个典型问题数据缺失某些坝址的生态影响数据缺失。我们采用同一区域类似坝址数据的均值进行插补并在论文中说明了这一处理及其潜在影响。量纲统一投资成本亿元、发电量亿千瓦时、淹没面积平方公里、就业人数万人。在计算前我们进行了Min-Max标准化将所有指标缩放到[0,1]区间便于后续综合计算。需求预测题目给出了历史电力需求数据。我们使用时间序列分析如ARIMA模型预测了未来30年的需求曲线并将其作为动态规划中必须满足的约束条件之一即到第t年累计装机容量必须大于等于预测需求。% 示例数据读取与初步处理 data readtable(dam_sites.csv); % 假设数据在CSV文件中 cost data.Cost; % 建设成本亿元 capacity data.Capacity; % 装机容量MW energy data.AnnualEnergy; % 年发电量亿kWh flood_area data.FloodArea; % 淹没面积km2 employment data.Employment; % 建设期就业人年 % Min-Max 标准化函数 function normalized minmax_normalize(raw_data) min_val min(raw_data); max_val max(raw_data); normalized (raw_data - min_val) / (max_val - min_val); end % 对成本进行标准化成本是越小越好在TOPSIS中属于成本型指标 normalized_cost minmax_normalize(cost); % 对发电量进行标准化发电量是越大越好效益型指标 normalized_energy minmax_normalize(energy);3.2 动态规划求解的核心代码结构动态规划的实现是重中之重。我们采用逆序递推法。由于状态是指数级的2^N直接枚举所有状态对于N15会非常慢。我们进行了两点优化可行性剪枝在每一阶段只生成满足当前投资预算约束和电力需求约束的状态。状态压缩用整数十进制的二进制位来表示电站集合状态例如整数5的二进制是101表示第1和第3个电站已建。这样可以用一个一维数组dp(time, state_index)来存储最优值。% 关键参数 N 10; % 电站数量 T 30; % 规划年数 total_states 2^N; % 理论状态总数实际会剪枝 % 初始化DP表dp_value(t, s) 表示第t年处于状态s时到期末的最大总效益 dp_value -inf(T, total_states); % 初始化为负无穷 dp_decision zeros(T, total_states); % 记录最优决策即开工哪个电站用整数编码 % 边界条件第T1年即规划期后所有状态的价值为0 % 在逆序递推中我们从第T年开始 % 逆序递推主循环 for t T:-1:1 for s 0:total_states-1 current_state s; % 如果当前状态不可行例如已超出投资能力跳过 if ~isFeasible(current_state, t) continue; end % 获取当前状态下所有可能的决策即今年可以开工的电站集合 possible_decisions getPossibleDecisions(current_state, t); max_value -inf; best_decision 0; for d_idx 1:length(possible_decisions) decision possible_decisions(d_idx); next_state current_state | decision; % 状态转移并集 % 计算阶段效益收益 - 成本 生态社会折算 stage_reward calculateReward(current_state, decision, t); % 查找下一阶段t1的状态价值 % 注意如果t是最后一年则下一阶段价值为0 if t T future_value 0; else future_value dp_value(t1, next_state 1); % MATLAB索引从1开始 end total_value stage_reward future_value; if total_value max_value max_value total_value; best_decision decision; end end if max_value -inf dp_value(t, s1) max_value; dp_decision(t, s1) best_decision; end end end % 顺推获取最优路径 optimal_path zeros(1, T); current_state 0; % 初始状态所有电站未建 for t 1:T dec dp_decision(t, current_state 1); optimal_path(t) dec; current_state current_state | dec; % 更新状态 end disp(最优年度开发决策序列整数编码:); disp(optimal_path);注意isFeasible、getPossibleDecisions、calculateReward这三个函数需要根据具体问题详细实现。calculateReward函数是模型的核心它需要将发电收益、建设成本、生态损失如淹没面积、社会效益如就业按照一定的折算系数统一到“效益”这个量纲下。这个折算系数需要根据文献或假设给出并在论文中重点讨论其敏感性。3.3 TOPSIS与熵权法的MATLAB实现这部分代码相对规整可以封装成函数。function [score, ranking, weights] entropyWeightedTOPSIS(dataMatrix, benefitCriteria) % entropyWeightedTOPSIS 使用熵权法确定权重并进行TOPSIS评价 % 输入 % dataMatrix: m*n 矩阵m个方案n个指标 % benefitCriteria: 1*n 逻辑向量true表示该指标为效益型越大越好false为成本型越小越好 % 输出 % score: 各方案的相对贴近度 C_i % ranking: 方案排名从优到劣 % weights: 熵权法计算出的权重 [m, n] size(dataMatrix); % 1. 数据标准化 (向量归一化) normMatrix dataMatrix ./ sqrt(sum(dataMatrix.^2, 1)); % 2. 熵权法计算权重 p normMatrix ./ sum(normMatrix, 1); % 计算比重 % 避免log(0)的情况加一个极小值 p(p 0) 1e-10; e -sum(p .* log(p), 1) / log(m); % 计算熵值 g 1 - e; % 计算差异系数 weights g / sum(g); % 计算权重 weights weights; % 转为列向量方便计算 % 3. 计算加权标准化矩阵 weightedMatrix normMatrix .* weights; % 4. 确定正负理想解 positiveIdeal zeros(1, n); negativeIdeal zeros(1, n); for j 1:n if benefitCriteria(j) positiveIdeal(j) max(weightedMatrix(:, j)); negativeIdeal(j) min(weightedMatrix(:, j)); else positiveIdeal(j) min(weightedMatrix(:, j)); negativeIdeal(j) max(weightedMatrix(:, j)); end end % 5. 计算各方案到正负理想解的距离 distToPositive sqrt(sum((weightedMatrix - positiveIdeal).^2, 2)); distToNegative sqrt(sum((weightedMatrix - negativeIdeal).^2, 2)); % 6. 计算相对贴近度 score distToNegative ./ (distToPositive distToNegative); % 7. 排序 [~, ranking] sort(score, descend); end % 调用示例 % 假设我们有5个方案4个评价指标发电量(益)、投资(成)、就业(益)、生态影响(成) X [ ... ]; % 5x4 的决策矩阵 isBenefit [true, false, true, false]; % 指标类型 [Ci, rank, w] entropyWeightedTOPSIS(X, isBenefit); fprintf(熵权法计算权重: %.4f, %.4f, %.4f, %.4f\n, w); fprintf(方案排名 (从优到劣): %s\n, mat2str(rank)); fprintf(各方案贴近度: \n); disp(Ci);4. 模型求解中的挑战与调优策略在实际编程和求解过程中我们遇到了几个典型的“坑”这里分享出来希望能帮你避雷。4.1 “维数灾难”与状态空间爆炸这是动态规划最经典的问题。10个电站就有1024种状态30个阶段理论上需要计算30*1024次。如果电站数增加到15状态数就是32768计算量急剧增加。我们的应对策略是约束剪枝在每一阶段只考虑满足“累计投资不超过当年总预算”和“累计装机满足当年最低需求”的状态。这能剪掉大量无效状态。滚动规划将30年分为6个5年规划期。先做第一个5年的精细动态规划固定前5年的最优决策后将其作为初始状态再进行下一个5年的规划。这相当于降低了阶段数。启发式初始化先用贪心算法每年选择“单位投资发电量”最高的未建电站生成一个较优的初始解然后在这个解附近的状态空间进行搜索而不是全局搜索。% 示例一个简单的可行性检查函数 function feasible isFeasible(state, current_year) global total_budget annual_budget demand_forecast installed_capacity cost; % 计算到当前年为止的总投资 invested calculateTotalInvestment(state); % 计算当前年的允许投资上限可能是逐年增加的 budget_limit sum(annual_budget(1:current_year)); % 计算当前状态下的总装机容量 total_cap calculateTotalCapacity(state); % 获取当前年的电力需求预测 current_demand demand_forecast(current_year); % 检查投资约束和需求约束 if invested budget_limit total_cap current_demand feasible true; else feasible false; end end4.2 多目标折算系数的敏感性在动态规划的calculateReward函数中我们需要把生态影响如淹没面积和社会效益如就业折算成“效益值”与经济效益相加。这个折算系数例如减少一平方公里淹没面积相当于增加多少亿元效益非常主观且对最终结果影响巨大。我们的处理方法是进行敏感性分析。我们设定了三套系数经济优先型生态社会折算系数低、平衡型、生态社会优先型折算系数高。分别运行模型得到三套不同的最优开发序列。然后在TOPSIS评价阶段将这三套序列都放入候选方案池并用一套相对客观的二级指标如实际的淹没总面积、总就业人数等进行最终评价。这样既考虑了不同价值取向又保证了评价过程的客观性。在论文中我们用专门的章节展示了敏感性分析的结果并讨论了不同政策导向下的规划差异。4.3 TOPSIS结果的分析与解读TOPSIS计算出的贴近度C_i是一个介于0到1之间的相对值。它只能用于方案间的排序其绝对值大小没有绝对意义。比如方案A的C_i0.65方案B的C_i0.60只能说A优于B但不能说A比B好5%。此外要仔细检查权重结果。如果某个指标的熵权极小接近0说明所有方案在这个指标上数据几乎一致该指标在本次决策中区分度低权重小是合理的。但如果一个你认为很重要的指标权重很小就需要回溯检查数据是否预处理不当或者指标本身是否需要调整。5. 完整流程复盘与竞赛心得回顾整个解题过程从看到赛题到提交论文是一个不断迭代和深化的过程。第一步问题分析至少2小时。全队一起精读题目列出所有已知条件、未知变量、约束条件和优化目标。用思维导图画出问题结构明确“多阶段决策”和“多目标评价”这两个核心特征从而锚定动态规划和TOPSIS-熵权法作为技术路线。第二步模型建立与数据准备约6小时。将自然语言描述转化为数学公式。定义清楚动态规划的五要素阶段、状态、决策、转移、效益。同时另一名队员开始清洗和预处理数据并编写数据可视化的脚本如历年需求预测图、坝址分布图这些图后来都放入了论文。第三步编程实现与调试约15小时跨度最长。这是最耗时的部分。我们先实现了动态规划的基础版本用5个电站、5年规划的小规模数据测试。通过单步调试确保状态转移和效益计算逻辑正确。然后加入约束剪枝再扩展到全规模。TOPSIS模块单独编写和测试。一个关键技巧是将核心参数如折算系数、预算增长率设为脚本开头的全局变量方便随时调整和进行敏感性分析。第四步结果分析与论文撰写约10小时。模型跑出结果不是终点。我们要分析为什么是这个建设顺序前几年集中建设小电站还是大电站方案对生态折算系数的敏感度如何我们绘制了“最优开发序列甘特图”、“累计效益随时间变化图”、“不同权重下的方案排名对比图”等。在论文中不仅陈述“我们做了什么”更重点解释“为什么结果是这样”、“这个结果意味着什么”。几点血泪教训早做敏感性分析不要等到模型全部跑完再做。在确定核心参数折算系数时就应用小规模数据测试不同参数下的结果趋势这能帮你理解模型的稳健性也能为论文提供丰富的分析素材。文档和注释同步编程时重要的函数开头一定要写清楚输入、输出和功能。变量名要有意义。这不仅能避免自己几天后看不懂代码在写论文“模型实现”部分时这些注释就是现成的素材。备份版本管理我们吃过亏。有一次调试时误改了核心函数导致结果全变又没备份差点崩溃。后来强制用Git进行简单版本管理每次重大修改前都提交一次。可视化即沟通评委看论文时间有限。一张清晰明了的图胜过大段文字。多用MATLAB的plot、bar、subplot做图确保图注清晰、坐标轴标签完整。最后我想说数学建模竞赛的魅力不在于找到一个“标准答案”而在于展示你用数学工具解决复杂现实问题的逻辑思维和综合能力。雅鲁藏布江开发问题没有唯一解但通过动态规划、TOPSIS这套方法我们能够系统性地生成并评价方案将决策过程从“拍脑袋”变为“有据可依”这本身就是建模的价值所在。希望这份超详细的拆解能让你在下次面对类似规划问题时手里有剑心中不慌。