☰
仿生群智算法求解无人机任务分配:MTSP建模与Python实现
2026/10/3 2:46:15 网站建设 项目流程

简介:本资源面向计算机、人工智能、自动化等专业的在校学生与算法学习者,提供一套基于仿生群智算法求解无人机任务分配(多旅行商问题)的Python课程设计源码。项目以群体智能大作业为背景,实现了ACO蚁群算法、GA遗传算法与PSO粒子群算法三种基线方案,并设置200个epochs迭代与Early Stop早停策略,便于对比不同算法的收敛表现。压缩包共7个文件,包含4个py源码、1个md说明文档、1个license及1个gitignore,整体约19KB,代码结构清晰、注释详尽,可直接运行验证。目前已有246人学习关注。读者可借此掌握多旅行商问题的建模思路、群智算法的实现细节与调参方法,也可在此基础上修改扩展,用于课程设计、毕业设计或项目初期立项演示,具备较高的学习与参考价值。

1. 从一份课设说起:仿生群智算法怎么把无人机任务分配讲明白

很多人第一次接触“无人机任务分配”是在课程设计里,题目往往长这样:给定若干目标和若干架无人机,每架无人机从基地出发,飞完分配给自己的目标后返回,要求总航程最短、负载均衡、不超时。把它抽象成数学模型,本质就是多旅行商问题(MTSP):多个销售员从同一城市出发,各自走一条闭合回路,覆盖所有城市且不重复,使总代价最小。区别在于无人机还有航程约束、转弯半径、任务类型差异,所以不能直接套经典 TSP 求解器。

仿生群智算法就是在这个背景下被引入的:蚁群、粒子群、人工蜂群、灰狼、麻雀搜索,这类算法不依赖梯度,能处理离散组合优化,还能通过信息素或位置更新自然实现并行搜索。对课设来说,它的价值在于代码量可控、参数可解释、结果可视化直观,答辩时能讲清楚“为什么这样迭代”。这篇笔记面向正在做这个题目的同学,也面向想把 MTSP 求解器落到实际调度场景的工程师,从建模、编码、参数到踩坑,一步步把可复现的 Python 源码结构讲透。

2. 把无人机任务分配写成 MTSP:建模与数据准备

2.1 为什么选 MTSP 而不是 VRP 或 TSP

单旅行商问题(TSP)只有一个闭环,多旅行商问题(MTSP)允许 m 条闭环,两者在解空间维度上差一个数量级。车辆路径问题(VRP)虽然也是多车,但通常带容量约束和取送货顺序,对课设来说约束太多,反而掩盖了群智算法的搜索机制。无人机任务分配更接近 MTSP 的变体:m 架无人机从同一基地出发,各自访问若干目标点后返回基地,目标点只访问一次,代价函数是总航程或最大单机航程。

常见做法是把 MTSP 转成 TSP 再求解:增加 m-1 个虚拟基地节点,让一条长路径在虚拟节点处断开成 m 段。这种编码简单,但虚拟节点的位置会影响解质量。我一般直接用“分段整数编码”:染色体长度等于目标点数,每个基因是目标编号,再用 m-1 个切点把序列切成 m 段,每段对应一架无人机的访问顺序。这样交叉变异都在目标序列上做,切点单独进化,逻辑清晰。

数据准备阶段需要三类输入:目标点坐标(或距离矩阵)、无人机数量 m、每架无人机的最大航程。坐标用二维数组存,距离矩阵用欧氏距离预计算,避免迭代中反复开方。下面这段代码就是标准的数据初始化,注意距离矩阵用np.linalg.norm向量化计算,比双重循环快一个量级。

import numpy as np def build_distance_matrix(coords): """ coords: shape (n, 2),第一行是基地,其余是目标点 返回: shape (n, n) 的对称距离矩阵 """ diff = coords[:, np.newaxis, :] - coords[np.newaxis, :, :] dist = np.linalg.norm(diff, axis=-1) np.fill_diagonal(dist, 0.0) return dist # 示例:1 个基地 + 20 个目标点 np.random.seed(42) coords = np.random.rand(21, 2) * 100 dist_matrix = build_distance_matrix(coords) print(dist_matrix.shape) # (21, 21)

逻辑说明:coords[:, np.newaxis, :] - coords[np.newaxis, :, :]利用广播生成 (n, n, 2) 的差值张量,再沿最后一维求范数,得到 (n, n) 距离矩阵。参数上,coords第一行必须是基地,后续所有索引 0 都代表基地,这样计算航程时直接取dist[0, first] + ... + dist[last, 0]。如果目标点超过 200 个,建议改用 KDTree 或直接读入外部距离矩阵,避免内存膨胀。

2.2 适应度函数:总航程、最大航程与负载均衡怎么加权

适应度函数决定了算法往哪个方向收敛。课设里最常见的翻车是只写总航程,结果一架无人机飞完全部点,其余无人机空载,答辩时被问“这算多旅行商吗”。正确的做法是把最大单机航程和航程标准差也纳入惩罚项。我一般用加权和:

fitness = w1 * total_distance + w2 * max_distance + w3 * std_distance + penalty

w1通常取 1.0,w2取 0.5 到 1.0,w3取 0.2 到 0.5。如果某架无人机超过最大航程,直接加一个很大的惩罚值(比如 1e6),让该个体在选择阶段被淘汰。下面是对应的 Python 实现,注意切点解码后要检查每段是否为空,空段意味着有无人机没任务,也要惩罚。

def decode_routes(sequence, cut_points, m): """ sequence: 目标点排列,长度 n-1(不含基地) cut_points: 长度为 m-1 的递增切点,范围 [1, len(sequence)-1] 返回: m 条路线,每条以 0(基地)开头和结尾 """ cuts = [0] + sorted(cut_points) + [len(sequence)] routes = [] for i in range(m): seg = sequence[cuts[i]:cuts[i+1]] routes.append([0] + list(seg) + [0]) return routes def fitness(sequence, cut_points, dist_matrix, m, max_range, weights=(1.0, 0.8, 0.3)): routes = decode_routes(sequence, cut_points, m) total, max_d, penalty = 0.0, 0.0, 0.0 lengths = [] for r in routes: if len(r) <= 2: # 空路线,只有基地往返 penalty += 1e5 lengths.append(0.0) continue d = sum(dist_matrix[r[i], r[i+1]] for i in range(len(r)-1)) lengths.append(d) total += d max_d = max(max_d, d) if d > max_range: penalty += 1e6 * (d - max_range) std_d = np.std(lengths) w1, w2, w3 = weights return w1 * total + w2 * max_d + w3 * std_d + penalty

逻辑说明:decode_routes把排列和切点还原成 m 条闭合路线,每条路线首尾都是 0。fitness里对空路线和超航程分别加惩罚,std_d用 NumPy 计算标准差。参数上,max_range根据无人机实际续航折算,课设里可以设为总平均航程的 1.5 倍;weights建议先用默认值跑通,再根据结果调整,如果发现某架无人机总是超载,就调大w2。

2.3 数据校验:三个容易忽略的边界

第一,目标点数量必须大于无人机数量,否则必然出现空路线,这时候要么减少 m,要么允许一架无人机访问多个点但切点重复。第二,距离矩阵必须对称且对角线为 0,如果从外部文件读入,先做np.allclose(dist, dist.T)检查。第三,坐标量纲要统一,如果经纬度直接当平面坐标用,距离会严重失真,建议先做墨卡托投影或直接用局部平面坐标。这三条在课设报告里写清楚,能省掉答辩时一半的追问。

3. 仿生群智算法选型:蚁群、粒子群还是人工蜂群

3.1 三种算法的搜索机制与 MTSP 适配度

蚁群算法(ACO)靠信息素和启发式因子构建路径,天然适合离散排列问题,但标准 ACO 是为 TSP 设计的,用到 MTSP 需要改状态转移规则:每只蚂蚁可以多次从基地出发,或者用 m 只蚂蚁同时构建 m 条路径。粒子群(PSO)原本是连续优化,用在 MTSP 上必须做离散化,常见做法是用随机键(random key)编码:粒子位置是连续向量,排序后得到目标访问顺序,再取前 m-1 个切点。人工蜂群(ABC)的雇佣蜂、观察蜂、侦察蜂三段式搜索,对排列问题可以用交换、逆序、插入三种邻域操作,实现简单,但收敛速度偏慢。

从课设角度,我一般推荐“离散粒子群 + 随机键”作为主线,因为代码最短、参数最少、可视化最直观;如果指导老师明确要求群智算法对比,就再加一个蚁群版本,用同一套适应度函数,最后画收敛曲线对比。下面给出离散粒子群的核心迭代代码,粒子位置是长度 n-1 + (m-1) 的连续向量,前段排序得到目标序列,后段排序得到切点。

class DiscretePSO: def __init__(self, n_targets, m, pop_size=50, max_iter=200): self.n = n_targets self.m = m self.dim = n_targets + (m - 1) self.pop_size = pop_size self.max_iter = max_iter self.w, self.c1, self.c2 = 0.7, 1.5, 1.5 def _decode(self, pos): seq = np.argsort(pos[:self.n]) + 1 # 目标编号从 1 开始 cuts = np.sort(pos[self.n:]) # 切点用连续值排序 cut_idx = np.argsort(cuts)[:self.m-1] + 1 cut_idx = np.clip(np.sort(cut_idx), 1, self.n - 1) return seq, cut_idx def run(self, dist_matrix, max_range): pos = np.random.rand(self.pop_size, self.dim) vel = np.random.randn(self.pop_size, self.dim) * 0.1 pbest = pos.copy() pbest_fit = np.array([fitness(*self._decode(p), dist_matrix, self.m, max_range) for p in pos]) gbest = pbest[np.argmin(pbest_fit)].copy() gbest_fit = np.min(pbest_fit) history = [] for it in range(self.max_iter): r1, r2 = np.random.rand(), np.random.rand() vel = (self.w * vel + self.c1 * r1 * (pbest - pos) + self.c2 * r2 * (gbest - pos)) pos = np.clip(pos + vel, 0, 1) for i in range(self.pop_size): seq, cuts = self._decode(pos[i]) f = fitness(seq, cuts, dist_matrix, self.m, max_range) if f < pbest_fit[i]: pbest[i], pbest_fit[i] = pos[i].copy(), f if f < gbest_fit: gbest, gbest_fit = pos[i].copy(), f history.append(gbest_fit) return self._decode(gbest), gbest_fit, history

逻辑说明:_decode把连续位置前 n 维排序得到目标序列,后 m-1 维排序后取索引作为切点,np.clip保证切点不越界。迭代中标准 PSO 的速度更新公式保留,位置限制在 [0,1] 区间,避免排序失效。参数上,pop_size取 30 到 80,max_iter取 200 到 500,惯性权重w可以从 0.9 线性降到 0.4,前期探索后期收敛。如果结果波动大,把c1调小、c2调大,让粒子更快向全局最优靠拢。

3.2 参数怎么设:种群、迭代、交叉变异概率

种群规模不是越大越好。20 个目标点、3 架无人机的课设规模,种群 50 足够,超过 100 只会让单次迭代变慢,收敛曲线反而更抖。迭代次数看收敛曲线,如果 150 代后适应度变化小于 1e-3,就可以停。交叉概率在离散 PSO 里对应的是“位置更新幅度”,如果发现早熟,把c1和c2都降到 1.0 以下,增加随机扰动。

对于蚁群版本,关键参数是信息素挥发率rho和启发式因子权重beta。rho取 0.1 到 0.3,太大导致信息素积累不足,太小导致搜索停滞。beta取 2 到 5,越大越贪心,容易陷入局部最优。人工蜂群的重点是观察蜂数量,一般设为雇佣蜂的一半,侦察蜂阈值limit取 20 到 50 代。

提示:参数没有万能值,建议先用小规模数据(10 个目标点、2 架无人机)跑通,记录每组参数的最优适应度,再放大到课设规模。不要一上来就调 500 代,浪费时间。

3.3 收敛曲线与路线图:怎么判断结果可信

收敛曲线要画两条:全局最优和种群平均。如果平均线一直贴着最优线,说明种群多样性不足,早熟了;如果平均线剧烈震荡,说明参数太激进。路线图用matplotlib画,基地用星号,不同无人机用不同颜色,箭头表示访问顺序。检查路线图时重点看三件事:有没有交叉路线(说明还有优化空间)、有没有某架无人机绕远路(负载不均)、有没有路线穿过禁飞区(如果题目有约束)。

import matplotlib.pyplot as plt def plot_routes(coords, routes, title="UAV Routes"): plt.figure(figsize=(8, 6)) colors = plt.cm.tab10(np.linspace(0, 1, len(routes))) for idx, r in enumerate(routes): pts = coords[r] plt.plot(pts[:, 0], pts[:, 1], '-o', color=colors[idx], label=f'UAV {idx+1}', markersize=4) plt.scatter(coords[0, 0], coords[0, 1], c='red', marker='*', s=200, label='Base') plt.legend() plt.title(title) plt.grid(True, alpha=0.3) plt.show()

逻辑说明:coords[r]按路线顺序取出坐标,plot连线并标点,基地单独用红色星号。参数上,figsize根据目标点密度调整,点太密时把markersize降到 2。如果路线交叉严重,回到适应度函数检查是否漏了最大航程惩罚。

4. 避坑与排查:课设里最容易翻车的五个地方

4.1 现象:算法跑完所有无人机都挤在一条路线上

原因:适应度函数只算总航程,没有对单机航程做惩罚,算法发现把所有点塞给一架无人机总距离最短。解决:在适应度里加入最大航程项和标准差项,权重从 0.5 起步,观察路线是否分散。如果仍然集中,检查切点解码是否把切点都映射到了序列末尾,导致前 m-1 段为空。

4.2 现象:收敛曲线前期下降很快,后期完全不动

原因:种群多样性丢失,所有粒子位置趋同。解决:在 PSO 里加入变异操作,每代以 0.05 到 0.1 的概率随机重置某个粒子的位置;或者把惯性权重设为线性递减,前期 0.9 后期 0.4。蚁群版本则提高挥发率rho到 0.3,让信息素更快更新。

4.3 现象:距离矩阵计算报错或结果异常大

原因:坐标数组形状不对,或者经纬度直接当平面坐标。解决:打印coords.shape确认是 (n, 2),用np.allclose(dist, dist.T)检查对称性。如果是经纬度,先用x = lon * 111 * cos(lat)做粗略投影,或者直接改用局部坐标系。

4.4 现象:切点解码后路线数量不对

原因:切点排序后没有去重,或者np.clip把多个切点压到同一个值。解决:在decode_routes里对切点做sorted(set(cut_points)),如果去重后数量不足 m-1,用随机值补齐。更稳妥的做法是在解码前检查切点唯一性,不满足就重新生成。

4.5 现象:换了随机种子结果差异巨大

原因:群智算法本身是随机搜索,单次运行不能代表算法性能。解决:固定随机种子做调试,但最终报告里要跑 10 次取平均值和标准差,画箱线图。如果标准差超过均值的 20%,说明参数不稳定,需要增大种群或迭代次数。

5. 进阶技巧:用局部搜索和并行化把解质量再提一档

群智算法跑完之后,最优解往往还留有明显的交叉路线。这时候加一段 2-opt 局部搜索,对每条路线内部做边交换,能在几十毫秒内把总航程再降 3% 到 8%。具体做法是:对每条路线,尝试交换任意两条边的端点,如果总距离减小就接受,重复直到没有改进。下面是一个针对单条路线的 2-opt 实现,注意基地节点 0 不参与交换。

def two_opt(route, dist_matrix): """route: 以 0 开头和结尾的列表,返回优化后的路线""" improved = True best = route[:] while improved: improved = False for i in range(1, len(best) - 2): for j in range(i + 1, len(best) - 1): if j - i == 1: continue new_route = best[:i] + best[i:j][::-1] + best[j:] old_d = sum(dist_matrix[best[k], best[k+1]] for k in range(len(best)-1)) new_d = sum(dist_matrix[new_route[k], new_route[k+1]] for k in range(len(new_route)-1)) if new_d < old_d - 1e-9: best = new_route improved = True # 一轮结束后重新开始,直到没有改进 return best

逻辑说明:外层while保证反复扫描直到收敛,内层双重循环枚举所有边对,best[i:j][::-1]实现片段反转。参数上,1e-9是浮点比较容差,避免死循环。如果目标点超过 50 个,2-opt 的 O(n²) 单轮会变慢,可以限制每轮只扫描前 30 个最近邻。

另一个提效手段是并行化:把种群分成 4 组,每组独立进化 50 代,再合并最优个体。Python 里用multiprocessing.Pool就能做,注意把距离矩阵和参数通过initializer传给子进程,避免反复序列化。实测在 8 核机器上,100 个目标点、5 架无人机的场景,并行版比串行版快 3 倍左右,解质量基本一致。

最后说一个我自己的习惯:每次调完参数,先把最优路线和收敛曲线存成图片,文件名带上参数组合,比如pso_pop50_iter300_w0.7.png。课设报告写到后期,你会感谢自己留了这些“后悔药”。仿生群智算法在无人机任务分配上不是银弹,但它能把一个 NP-hard 问题拆成可解释、可调参、可可视化的迭代过程,这对课设和实际调度都够用了。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询