简介这份资源是一套基于Python开发的粒子群算法多无人机任务分配源码主要面向计算机、通信、人工智能、自动化等相关专业学生及毕业设计者用于解决多无人机协同场景下的任务分配与路径优化问题。代码已经过调试并确保可运行项目共15个文件压缩包约1.41MB其中9个Python脚本分别承担主程序、粒子群优化、距离计算、适应度评估、约束条件判断、时间统计等核心功能4张图片直观展示飞行轨迹与任务分配结果说明文档则提供项目结构与使用指引。目前已有168人学习适合作为期末课程设计、大作业或毕业设计的基础框架也适合入门者循序学习。通过运行示例与研读源码可掌握粒子群算法在任务分配中的建模流程、参数调节策略及结果可视化方法并可在现有基础上扩展业务逻辑或替换算法具备较强的学习借鉴与二次开发价值。1. 粒子群算法多无人机任务分配毕设代码拿到手先确认这件事每年毕业季都会有人问多无人机任务分配用什么算法比较稳答案十有八九是粒子群算法PSO。原因很直接任务分配本质是组合优化问题无人机数量一多穷举就炸了粒子群实现简单、收敛快、代码量少拿来做毕设正合适。这套基于 Python 开发的粒子群算法多无人机任务分配源码跑起来之后的实际效果是——给定一组任务点、一组无人机算法输出一份分配方案告诉你是哪架无人机、按什么顺序、去执行哪几个任务同时把飞行距离、时间窗、约束惩罚都算进去最后直接画出甘特图和飞行轨迹图。适合正在做毕设或课程设计的在读学生也适合想快速搭一个分配算法基线再往上改的从业者。代码包不大但结构完整main.py串全局pso.py管搜索condition.py管约束plots.py画图属于那种可以边跑边改的工程结构。下面按文件拆开说。2. 项目骨架先看懂每个文件干的活再谈跑通2.1 源码包里的文件和基本逻辑整个项目拆开之后是 13 个文件没有多余的微信小程序壳和网页工程就是一套纯 Python 的算法实现。我列一下文件和它的岗位职责文件职责main.py算法流程入口负责初始化、调用 PSO、输出结果globalv.py全局变量与初始参数任务数、无人机数、种群大小等distance.py构建距离矩阵计算两个任务点之间的飞行代价time_s.py时间相关参数生成包括时间窗和任务耗时decode.py粒子解码把位置向量转成任务分配方案fit_dis.py适应度计算对应粒子的总目标函数值condition.py约束条件处理不满足就加惩罚pso.py粒子群算法核心速度更新、位置更新、全局最优维护plots.py可视化模块输出甘特图、散点图、轨迹图tu_gante.png/tu_fly.png/tu_scatter.png/tu_diagram.png作者跑完样例生成的输出图可以直接预览效果这个分工是一个很标准的主流程 基础数据 算法核心 结果后处理四层结构。理解它的关键是不要一上来就扎进 PSO 里面看先搞清楚数据是怎么从任务点坐标、无人机初始位置流到解码后的分配表的。2.2 main.py 的调度流程我一般拿到一份毕设代码第一件事不是跑而是先打开main.py看流程骨架。这份代码的主流程大概是这样的import numpy as np from globalv import set_global_params from distance import build_distance_matrix from time_s import generate_time_windows from decode import decode_particle from fit_dis import calc_fitness from condition import evaluate_constraints from pso import run_pso from plots import plot_gantt, plot_scatter, plot_flight if __name__ __main__: # 1. 设置全局参数任务数、无人机数、种群大小、迭代次数 params set_global_params(task_num20, uav_num5, pop_size100, max_iter200) # 2. 加载任务坐标构建距离矩阵和时间窗 distance_matrix build_distance_matrix(params[task_coords]) time_windows generate_time_windows(params[task_num]) # 3. 运行粒子群算法返回最优粒子和最优适应度 best_position, best_fitness, history run_pso(params, distance_matrix, time_windows) # 4. 解码最优粒子得到分配方案 best_scheme decode_particle(best_position, params[uav_num]) # 5. 输出结果与可视化 print(最优适应度:, best_fitness) print(分配方案:\n, best_scheme) plot_scatter(best_scheme, params[task_coords]) plot_gantt(best_scheme, time_windows)代码逻辑并不复杂三句话就能讲完先把环境变量和距离矩阵初始化好然后丢给粒子群算法去搜最后把最优解解码成人类能看懂的表格并画图。有一点值得注意decode_particle被单独拆成一个文件这在学生写的毕设里比较少见。很多人的习惯是把解码逻辑写在pso.py里而这个项目拆出来主要是因为解码策略是独立于搜索过程的一层后面你想换编码方式和分配规则时只需要动decode.py不需要碰 PSO 更新那部分。这个设计作为毕设代码来答辩老师一问你模块之间怎么解耦的你就有话说了。2.3 全局变量和初始方案生成globalv.py我拆开看过它做的事很简单把散落在各个函数里需要的参数集中管理。核心参数如下import numpy as np def set_global_params(task_num20, uav_num5, pop_size100, max_iter200): # 随机种子固定保证实验可复现 np.random.seed(42) params { task_num: task_num, # 任务数量 uav_num: uav_num, # 无人机数量 pop_size: pop_size, # 粒子群种群规模 max_iter: max_iter, # 最大迭代次数 dim: task_num, # 粒子维度任务数每个维度对应一个任务 task_coords: np.random.rand(task_num, 2) * 100, # 任务坐标 0-100 范围 uav_start: np.random.rand(uav_num, 2) * 100, # 无人机起点坐标 w: 0.8, # 粒子群惯性权重 c1: 2.0, # 个体学习因子 c2: 2.0, # 社会学习因子 v_max: 2.0, # 速度上限 x_min: 0.0, # 位置下界 x_max: 100.0, # 位置上界 range_table: [0, 1, 2, 3, 4] # 无人机编号范围多无人机索引 } return params这里我提一句有些同学拿到项目之后跑出来效果一般直接怀疑代码有问题其实大部分时候不是代码问题而是np.random.seed(42)这个种子写死了换一组任务坐标结果就变了。后面避坑章节我会专门讲随机种子的事。那么初始方案是从哪来的其实粒子群算法本身是从一个随机种群开始的每个粒子对应一份随机的任务到无人机的映射然后算法通过几十上百次迭代慢慢把这些粒子推向最优解区域。这里初始化做的比较简单但也够用——任务坐标是 0-100 的均匀随机数无人机起点也是这样生成相当于一个完全虚拟的仿真场景。3. 核心算法PSO 编码方式、适应度函数与约束惩罚3.1 任务分配问题的编码方式是怎么解决的多无人机任务分配在数学上可以描述成有 M 架无人机、N 个任务点每架无人机从起点出发需要访问若干任务点目标是让总飞行代价最小且满足时间窗和单机最大任务量约束。这一步难在怎么把谁去哪、先去谁后去谁表示成粒子群算法能搜的向量。我看了decode.py的实现它用的是一种很常见的连续实数编码import numpy as np def decode_particle(position, uav_num): 把连续实数位置向量解码为任务分配方案 position: 长度为 task_num 的实数向量每个维度对应一个任务序号 uav_num: 无人机数量 返回: scheme[i] 被分配到的无人机编号 task_num len(position) scheme np.zeros(task_num, dtypeint) for i in range(task_num): # 对每个任务用取模映射到无人机编号区间 val int(round(position[i])) % uav_num scheme[i] val return scheme这段代码的思路其实并不复杂PSO 中位置向量里的每个维度对应一个任务粒子的第 i 维数值经过取整、取模之后就映射成了无人机编号。比如第 3 个维度值是 9.4取整后是 9对 5 架无人机取模得到 4那任务 3 就分配给 4 号无人机。这种编码方式优点是实现简单速度快符合粒子群算法的连续搜索特性不需要做任何离散化处理。缺点也很明显——它天然不保证每个无人机的任务量均匀。完全有可能出现 4 号无人机接了 8 个任务1 号无人机只接了 1 个任务的情况。所以condition.py里就要做负载约束的惩罚这是这个项目处理问题的组合拳编码负责把搜索空间变成 PSO 能搜的样子约束惩罚负责把非法解拉回来。对于毕设场景我完全赞成这个设计。真实工程里一般会用基于置换编码或者两段式编码设计起来更复杂但作为课程设计和毕业论文连续实值解码 约束惩罚已经能把问题说明白。3.2 适应度函数飞行距离、时间窗和惩罚项怎么算适应度函数是整个优化过程唯一的评价标准直接决定粒子往哪个方向飞。fit_dis.py里做了这几件事计算任务点之间的距离代价叠加时间窗早到或者晚到的惩罚最后加上约束违规的惩罚值。简化后的逻辑如下import numpy as np from distance import build_distance_matrix def calc_fitness(position, distance_matrix, time_windows, uav_num, start_pos): 计算某个粒子(即某个分配方案)的总适应度 返回值越小方案越好 task_num len(position) scheme decode_particle(position, uav_num) total_cost 0.0 time_penalty 0.0 load_penalty 0.0 # 统计各无人机分配的任务量用于负载均衡 load_count np.zeros(uav_num) for task_id in range(task_num): uav_id scheme[task_id] load_count[uav_id] 1 # 距离代价无人机从起点出发按任务编号顺序访问 for uav_id in range(uav_num): assigned_tasks np.where(scheme uav_id)[0] if len(assigned_tasks) 0: continue # 从无人机起点到第一个任务点 cur_pos start_pos[uav_id] for t in assigned_tasks: task_pos distance_matrix[coords][t] total_cost np.linalg.norm(cur_pos - task_pos) cur_pos task_pos # 时间窗惩罚早于最早时间或晚于最晚时间都要罚 for t in range(task_num): start_time time_windows[t][0] end_time time_windows[t][1] arrive_time estimate_arrive_time(position, t) if arrive_time start_time: time_penalty (start_time - arrive_time) * 1.5 elif arrive_time end_time: time_penalty (arrive_time - end_time) * 2.0 # 负载惩罚单机任务数超过任务总量一半则惩罚 for u in range(uav_num): if load_count[u] task_num / uav_num * 1.5: load_penalty 100.0 return total_cost time_penalty load_penalty这个函数给三个目标距离、时间、负载分别算了一项然后直接相加作为总代价。距离项是主体时间窗是软约束负载惩罚是硬约束。有两点值得注意。第一时间惩罚系数不再是统一的 1.0 而是早到罚和晚到罚系数不同1.5 和 2.0。这在语义上是对的晚到意味着任务可能已经失效代价比早更大。第二负载惩罚用了 100.0这批样本中距离成本的数量级是百级所以这个惩罚量级可以生效量级的设定非常重要。3.3 约束条件是怎么加的condition.py是很多人看不懂的文件因为代码量不大但函数名很长。拆开看核心其实是评估惩罚两步def evaluate_constraints(scheme, uav_num, task_num): 返回一个二元组 (violation_count, penalty_value) load_count np.zeros(uav_num) for task_id in range(task_num): uav_id scheme[task_id] load_count[uav_id] 1 violation_count 0 penalty_value 0.0 # 约束一单机任务上限任务数超出负载上限则违规 max_load int(np.ceil(task_num / uav_num * 1.5)) for u in range(uav_num): if load_count[u] max_load: violation_count 1 penalty_value 100.0 # 约束二每个任务必须被分配且只能分配一次编码天然保证 # 约束三所有无人机必须至少分配一个任务 for u in range(uav_num): if load_count[u] 0: violation_count 1 penalty_value 150.0 return violation_count, penalty_value这里比较巧妙的是每个任务必须分配一次这个约束在编码阶段就已经天然满足了。因为 decode 的时候每个维度对应一个任务每个任务只取模一次不可能出现两个任务争抢一个位置的情况。所以约束处理只需要检查无人机的负载上下限就行。关于约束这个事我想说一句很多毕设代码在约束处理上写的会比这复杂得多比如用修复算子、局部调整算法甚至混合遗传算法的变异来修复非法解。但这套代码选择了最朴素的罚函数法它适合粒子群算法的骨架因为粒子群迭代过程中不需要处理离散可行性问题只要让适应度里带着惩罚值粒子自然会被推开。3.4 PSO 迭代更新速度、位置和边界处理pso.py是整个项目的算法心脏。标准的 PSO 更新公式大家都见过v w * v c1 * r1 * (pbest - x) c2 * r2 * (gbest - x)x x v代码实现关键部分长这样import numpy as np from fit_dis import calc_fitness def run_pso(params, distance_matrix, time_windows): task_num params[task_num] uav_num params[uav_num] pop_size params[pop_size] max_iter params[max_iter] dim params[dim] # 初始化粒子群位置和速度 positions np.random.uniform(params[x_min], params[x_max], size(pop_size, dim)) velocities np.random.uniform(-params[v_max], params[v_max], size(pop_size, dim)) # 个体最优和全局最优初始化 pbest_positions positions.copy() pbest_fitness np.array([calc_fitness(p, distance_matrix, time_windows, uav_num, params[uav_start]) for p in positions]) gbest_idx np.argmin(pbest_fitness) gbest_position pbest_positions[gbest_idx].copy() gbest_fitness pbest_fitness[gbest_idx] # 迭代搜索 for it in range(max_iter): # 惯性权重线性递减0.9 - 0.4常用的策略 w 0.9 - (it / max_iter) * 0.5 for i in range(pop_size): r1 np.random.rand(dim) r2 np.random.rand(dim) # 速度更新 velocities[i] (w * velocities[i] params[c1] * r1 * (pbest_positions[i] - positions[i]) params[c2] * r2 * (gbest_position - positions[i])) # 限速防止粒子飞出可行域 velocities[i] np.clip(velocities[i], -params[v_max], params[v_max]) # 位置更新 positions[i] positions[i] velocities[i] # 越界处理超出边界就拉回边界 positions[i] np.clip(positions[i], params[x_min], params[x_max]) # 计算新适应度并更新个体最优 fitness calc_fitness(positions[i], distance_matrix, time_windows, uav_num, params[uav_start]) if fitness pbest_fitness[i]: pbest_fitness[i] fitness pbest_positions[i] positions[i].copy() # 更新全局最优 best_idx np.argmin(pbest_fitness) if pbest_fitness[best_idx] gbest_fitness: gbest_fitness pbest_fitness[best_idx] gbest_position pbest_positions[best_idx].copy() return gbest_position, gbest_fitness, None这个实现里有几个细节值得留意。第一惯性权重 w 从 0.9 线性递减到 0.4这是粒子群算法里非常经典的策略前期大权重保证全局搜索能力后期小权重让粒子在局部精细搜索。第二速度做了 clip防止更新量过大一下子把粒子踢到十万八千里之外。第三越界处理直接把位置拉回到边界而不是随机重置,这种方式实现简单但缺点是有可能大量粒子堆在边界。我在实际跑这个代码的时候把pop_size从 100 调到 50max_iter从 200 调到 100发现结果仍然可以收敛但分配方案的稳定性稍微差了一点。这说明这套代码的参数冗余度还是够用的,不至于稍微调一下参数就崩掉。4. 参数调节哪些参数值得调哪些不值得4.1 全局参数与算法超参对照拿到代码第一件事不是改算法而是先理解参数。globalv.py里同时混合了问题设定参数和算法超参两类新手容易混着调。我把它们拆开列一张表参数位置类型含义调整建议task_numglobalv问题任务点数量根据题目要求设uav_numglobalv问题无人机数量根据题目要求设pop_sizeglobalv算法种群规模常用 50-200越大收敛越稳但越慢max_iterglobalv算法最大迭代次数常用 100-500看收敛曲线再定dimglobalv问题粒子维度 任务数不要单独改跟着task_num走wglobalv算法惯性权重0.4-0.9体现全局/局部搜索平衡c1globalv算法个体学习因子1.5-2.5c2globalv算法社会学习因子1.5-2.5v_maxglobalv算法最大速度位置范围的 5%-20% 比较合适x_min / x_maxglobalv问题位置边界跟坐标范围一致即可我在跑这份代码的时候最常调的就是pop_size、max_iter、w这三个。因为任务分配问题的计算量主要来自适应度函数里的距离矩阵计算种群越大、迭代次数越多总运行时间线性上涨。4.2 关键超参的调参经验粒子群算法调参本质上是在调三件事搜索范围够不够大、收敛速度够不够快、会不会陷入局部最优。惯性权重w是第一个值得调的参数。0.9偏大粒子动量足前期探索性强适合任务点分布比较分散的场景0.4接近下限后期收敛快适合比较密集的任务群。这套代码里用了线性递减一般不用动。如果你发现运行结果迭代到一半就停滞可以把递减速度放慢比如从0.95递减到0.4相当于在后期多保持一点搜索能力。学习因子c1和c2我建议保持默认值 2.0。这两个参数在 PSO 里比较玄学调得太悬殊会让粒子飞向一边倒的极端情况比如c1太大粒子只顾自己的历史最优不去看全局最优收敛速度会明显变慢c2太大则容易过早被某个局部最优吸引多样性变差。速度上限v_max是另一个容易忽略的参数。坐标范围如果是 0-100v_max2.0意味着粒子每代最多移动 2 个单位100 次迭代最多移动 200 个单位,搜索空间是完全足够的。如果你把v_max设成 50粒子一两步就能从场地一头飞到另一头后期收敛时会来回震荡得厉害。种子np.random.seed(42)是值得换的。默认 seed 是 42每次跑出来的结果完全一样。你想让结果更稳定、更有可对比性可以把它固定成别的值但如果你发现自己的代码怎么调都在一个局部最优附近打转可以换一个 seed 试试,有时候真的能换个运气。4.3 调参流程先看收敛曲线再看分配结果我一般调参数的时候不会盲目改。第一步跑一次默认参数用plots.py画出来的图看两件事最优适应度值是多少、收敛速度快不快。第二步把max_iter加大到 300看最优适应度有没有继续下降,如果降幅特别小说明当前参数已经在收敛不需要再增加迭代次数如果降幅很明显说明迭代次数不够需要继续加。第三步调w的策略。如果收敛曲线是一条直线直接掉到底说明它收敛得太快大概率是陷入局部最优如果曲线下降得很慢但一直在降说明搜索过程健康可以加大迭代次数获取更好结果。最后一步才是调c1和c2。我基本不动这两个参数。因为在一个毕设里PSO 调参并不是创新点答辩老师更关注的是你问题建模清不清楚、约束处理对不对、结果有没有说服力。花太多时间在参数上反而容易被问住。5. 避坑这套代码运行时最常见的六个实际问题5.1 跑出来的分配方案总是集中在少数无人机上现象任务点有 20 个无人机有 5 架但最后结果里 3 号无人机执行了 9 个任务另外两架无人机只分了 1-2 个任务负载明显不平衡。原因连续实值编码加取模映射天然无法保证负载均衡。如果任务点在空间上分布不均匀PSO 很容易把大部分任务分配给距离中心最近的无人机因为这样距离代价最小,负载惩罚虽然能拦一部分但惩罚系数不够大的时候拦不住。解决把condition.py里的负载惩罚系数从100.0提到200.0或300.0。我试过这个项目当惩罚系数到 300 时分配结果会明显变得更分散。如果你要更精确的控制可以把任务数超过平均值 1.5 倍改成超过平均值 1.2 倍。5.2 迭代到一半适应度就停住不动了现象pso.py运行中前 30 代适应度明显下降但到了第 50 代左右最优适应度完全不变后面 150 代都在原地踏。原因这是粒子群算法典型的早熟收敛(premature convergence)种群多样性丢失了。所有粒子都飞到同一个局部最优附近pbest和gbest基本重合速度更新里(pbest - x)和(gbest - x)都趋近于零粒子被粘住了。解决最简单的办法是加大惯性权重w的起始值从 0.9 改成 0.95让粒子在前期飞得“野”一点。或者把v_max从2.0提到4.0保证粒子在迭代后期还能有一定的动量冲出局部最优。如果两种方法都不见效那说明问题规模本身就比较简单50 代就已经收敛了加迭代次数并没有意义。5.3 每次跑出来的结果都不一样没法对比现象同一个参数、同一个任务集今天跑出来的最优适应度是 1523明天再跑可能变成 1487写报告时不知道怎么选。原因全局变量里虽然有np.random.seed(42)但如果你在main.py里调整过代码顺序或者在globalv.py之外还有随机数生成逻辑种子就会失效。另外如果你改过pop_size或者max_iter随机数序列也会完全变化。解决把随机种子固定写在main.py的最顶部确保每次运行的随机序列一致。然后做对比实验的时候保持 seed 不变只改你想测的参数。这段话我写了很多次对比实验的唯一原则是控制变量随机种子也是变量之一。5.4 距离矩阵算出来的数值量级不对现象任务坐标在 0-100 之间但距离矩阵里出现几千甚至上万的数值粒子适应度直接爆表收敛曲线画出来像一条竖线。原因看distance.py里用的距离公式。如果坐标单位是经纬度或者从外部导入的数据没有做归一化坐标范围可能不在 0-100 而是 0-10000距离计算出来自然大几个量级。还有一些人的程序里混用了欧氏距离和曼哈顿距离导致结果量级对不上。解决给距离矩阵加一个归一化或者把所有坐标统一缩放。可以在globalv.py里设置一个coord_scale100.0在计算距离前对坐标做/ coord_scale处理。顺便检查distance.py里算的是不是欧氏距离别用成|x1-x2| |y1-y2|。5.5 Matplotlib 画图报错或者显示不了中文现象plots.py运行到plt.show()时直接报错或者在甘特图的图例标题上显示一堆方框。原因tu_gante.png、tu_scatter.png这些输出图需要 Matplotlib 的支持而很多 Python 环境里 Matplotlib 没有配好中文字体或者后端不是适合当前系统的。报错信息通常是font family not found或者backend相关的。解决在plots.py开头强制设置中文字体和后端import matplotlib matplotlib.use(Agg) # 不使用 GUI 后端直接保存图片 import matplotlib.pyplot as plt # 设置中文字体避免乱码 plt.rcParams[font.sans-serif] [SimHei, DejaVu Sans] plt.rcParams[axes.unicode_minus] False这样处理之后甘特图、散点图里的中文注释就不会变成乱码了。5.6 把task_num改成 50 之后跑起来特别慢现象默认参数 20 个任务、5 架无人机跑完大概十几秒。把任务数翻到 50运行时间变成几分钟。原因适应度函数的时间复杂度是 O(task_num × uav_num)加上粒子群算法的复杂度 O(pop_size × max_iter × task_num)。任务数从 20 变成 50计算量约等于原来的 2.5 倍再叠加迭代次数慢是正常的。解决优先减少pop_size比如从 100 降到 50然后增加max_iter。因为大任务数的情况下初始随机粒子的方案已经比较分散种群太大对搜索收益不大反而浪费时间。如果你要跑 50 任务 × 10 无人机的场景建议把任务坐标也重新随机生成让分布更有代表性。6. 进阶用法把这份源码改成能写进论文里的完整实验基础版本跑通之后很多人以为毕业设计就完事了其实不然。答辩老师大概率会问你这个算法跟别的算法比有什么优势如果你没有对比实验就很难答好。这里我分享两个最实用的进阶方向。第一个是加对比算法。最常见的是把 PSO 跟贪心算法做对比。贪心算法的逻辑是每个任务从当前最近的无人机找起直接分配。代码量不超过 30 行但能得出一个明确的baseline 数值跟 PSO 的结果一对比就能体现粒子群搜索的优越性。第二个是画收敛曲线。pso.py返回的history参数目前没有实际使用你可以每迭代 10 次记录一次gbest_fitness最后用 Matplotlib 画一条适应度随迭代次数下降的曲线。这张图放在论文里非常管用一眼就能看出算法是否收敛。第三个改动方向是加一个简单的杂交操作来改善早熟问题在每代迭代结束后随机选择两个粒子把位置向量的一部分交叉重组合。这个是受遗传算法启发能有效增加种群多样性代码改动也小。你可以先用这套代码跑出默认结果然后替换成你的任务数据调整参数画出收敛曲线跟贪心算法对比把三张图放进论文——整个故事就完整了问题定义、算法设计、结果对比、收敛性分析全都有。从那以后我每次做智能算法的对比实验都强制走一遍固定种子 → 跑贪心 baseline → 跑 PSO → 画收敛曲线的流程再也没被答辩老师问倒过。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?