启发式优化算法简介

一、启发式算法到底解决什么问题

优化问题的基本形式通常可以写成:

\\\min_{x\\in\\Omega} f(x) \\

或者:

\\\max_{x\\in\\Omega} f(x) \\

其中,\(x\) 是一个候选方案,\(\Omega\) 是所有可行方案组成的搜索空间,\(f(x)\) 是衡量方案质量的目标函数。

很多现实问题虽然容易描述,却很难精确求解。例如:

  • 一辆车应该按什么顺序访问几十个客户,才能使总路程最短;
  • 几百个任务应该怎样分配给多台机器,才能使完工时间最短;
  • 应该从上千个特征中选择哪些特征,才能提高模型性能;
  • 控制器的多个参数应该取什么值,才能使系统响应最好;
  • 在预算、时间、容量等限制下,应该选择哪些项目;
  • 神经网络超参数应该如何组合,才能获得较好的验证结果。

这些问题的共同特点是搜索空间很大。假设有 \(n\) 个物品,每个物品只有"选"和"不选"两种状态,则方案数量为:

\2\^n \\

当 \(n=100\) 时,方案数量约为:

\2\^{100}\\approx1.27\\times10\^{30} \\

如果问题是对 \(n\) 个任务进行排序,方案数量为:

\n! \\

当 \(n=50\) 时,\(50!\) 已经远远超过能够穷举的范围。

启发式算法的目标不是把所有方案都检查一遍,而是利用问题结构、随机性、历史经验或群体协作,在有限时间内搜索到质量较高的方案。

它们通常具有以下特点:

  • 不要求目标函数可导;
  • 可以处理离散、连续、排列和混合变量;
  • 可以处理复杂约束;
  • 适用于黑盒函数;
  • 一般不能保证一定找到全局最优解;
  • 算法结果可能具有随机性;
  • 实际效果高度依赖问题表示和搜索操作。

学习启发式算法时,最重要的不是记住某种自然现象的比喻,而是理解三个共同问题:

  1. 一个解怎样表示;
  2. 新解怎样产生;
  3. 怎样平衡探索和利用。

二、探索与利用是所有算法的共同核心

启发式搜索始终面临两个相反的目标。

探索是指搜索新的区域,希望发现此前没有见过的优良方案。利用是指围绕已经发现的好方案继续优化,希望把当前结果做得更精细。

如果探索过弱,算法会很快聚集在某个局部最优附近;如果探索过强,算法会像随机搜索一样四处跳动,难以积累已有经验。

不同算法使用不同机制平衡探索与利用:

  • 局部搜索主要利用当前区域;
  • 模拟退火通过温度控制接受较差解的概率;
  • 遗传算法用选择进行利用,用交叉和变异进行探索;
  • 粒子群通过个体最优、群体最优和惯性共同控制运动;
  • 蚁群算法通过信息素强化优秀路径,通过挥发和随机选路保持探索;
  • 禁忌搜索通过记忆机制避免搜索循环;
  • 变邻域搜索通过切换邻域改变搜索尺度。

可以把一个通用启发式算法抽象为:

text 复制代码
生成初始解或初始种群
评估候选解质量

重复:
    根据当前信息生成新解
    决定是否接受或保留新解
    更新搜索经验
    调整探索强度
直到满足停止条件

输出搜索过程中发现的最好解

这里的"搜索经验"可以是历史最优位置、信息素、禁忌表、种群结构或温度状态。

三、局部搜索

3.1 核心思想

局部搜索从一个当前解出发,不断检查它附近的方案。如果发现更好的邻域解,就移动过去,然后继续搜索。

对于最小化问题,设当前解为 \(x\),它的邻域为 \(N(x)\)。如果存在:

\x'\\in N(x) \\

并且:

\f(x')\

则可以用 \(x'\) 替换当前解。

局部搜索的关键不是某个固定公式,而是邻域函数。邻域函数决定什么样的方案被认为是当前方案的"附近"。

3.2 邻域的含义

在连续优化中,邻域可以是给变量加入小扰动:

\x'=x+\\epsilon \\

其中 \(\epsilon\) 可以服从均匀分布或正态分布。

在二进制问题中,邻域可以是随机翻转一个基因:

text 复制代码
101001 → 101101

在任务排序问题中,邻域可以是交换两个任务:

text 复制代码
[1, 2, 3, 4, 5]
→
[1, 4, 3, 2, 5]

在旅行商问题中,常见邻域包括:

  • 交换两个城市;
  • 插入一个城市;
  • 反转一段路线;
  • 2-opt;
  • 3-opt。

2-opt 的思想是删除路线中的两条边,再以另一种方式连接,从而消除路线交叉或缩短总距离。

3.3 邻域选择策略

局部搜索并不一定检查全部邻域,可以采用不同策略。

第一改进策略:按某种顺序检查邻域,一旦发现第一个更好的解就立即移动。它速度快,但不一定选择改进幅度最大的方案。

最优改进策略:检查整个邻域,选择其中最好的解。它单次改进更充分,但计算量更大。

随机改进策略:随机选择邻域解,只要满足条件就接受。它实现简单,也能增加一定随机性。

3.4 核心流程

text 复制代码
随机生成初始解 current
best = current

重复:
    从 current 的邻域中生成候选解 candidate

    如果 candidate 更好:
        current = candidate

        如果 current 优于 best:
            best = current
    否则:
        保持 current 不变

直到邻域中无法继续改善
输出 best

3.5 为什么会陷入局部最优

局部最优是指当前解比所有邻域解都好:

\f(x)\\leq f(x'),\\qquad \\forall x'\\in N(x) \\

但它不一定比整个搜索空间中的所有解都好。

局部搜索只能看到当前邻域。如果通往更优区域的路径必须先经过一个较差解,它就无法继续前进。

这也是后续算法要解决的核心问题:

  • 模拟退火允许暂时接受较差解;
  • 禁忌搜索允许移动到较差解,但禁止立即返回;
  • 变邻域搜索改变邻域结构;
  • 遗传算法同时维护多个搜索区域;
  • 粒子群通过群体经验移动;
  • 蚁群通过概率构造新方案。

3.6 适用场景

局部搜索适合:

  • 可以快速评价邻域变化的问题;
  • 邻域结构明确的问题;
  • 需要快速获得一个较好方案的问题;
  • 作为其他算法的局部改进模块。

很多优秀算法并不是完全替代局部搜索,而是负责把搜索带到新的区域,再让局部搜索进行精细优化。

四、模拟退火算法

4.1 核心思想

模拟退火在局部搜索的基础上增加了一个关键机制:新解更差时,不一定拒绝,而是按一定概率接受。

对于最小化问题,定义:

\\\Delta E=f(x')-f(x) \\

如果:

\\\Delta E\\leq0 \\

说明新解更好,直接接受。

如果:

\\\Delta E\>0 \\

说明新解更差,此时按照下面的概率接受:

\P=\\exp\\left(-\\frac{\\Delta E}{T}\\right) \\

其中 \(T\) 是当前温度。

4.2 为什么接受较差解

假设当前解位于一个局部最低点,周围所有小范围移动都会让目标值变差。如果算法永远拒绝较差解,就会停在这里。

模拟退火允许先走到一个稍差的位置,再从那里进入新的区域。虽然短期看是退步,但可能换来长期更好的结果。

因此,接受较差解不是算法犯错,而是一种有控制的探索策略。

4.3 温度的作用

当温度较高时,较差解也有较高概率被接受。算法前期更像大范围随机探索。

当温度较低时,接受较差解的概率迅速降低。算法后期逐渐变成稳定的局部搜索。

例如 \(\Delta E=5\):

当 \(T=100\) 时:

\P=e\^{-5/100}\\approx0.951 \\

当 \(T=10\) 时:

\P=e\^{-5/10}\\approx0.607 \\

当 \(T=1\) 时:

\P=e\^{-5}\\approx0.0067 \\

温度越低,算法越不愿意冒险。

4.4 当前解与历史最优解

模拟退火必须同时维护两个解:

  • 当前解:用于继续搜索,可以变好,也可以变差;
  • 历史最优解:记录搜索过程中发现的最好结果,只会改善。

如果把两者混为一谈,那么一旦接受较差解,就会丢失之前找到的好结果。

4.5 降温策略

最常见的是几何降温:

\T_{k+1}=\\alpha T_k \\

其中:

\0\<\\alpha\<1 \\

例如 \(\alpha=0.95\) 时,温度依次为:

\100,\\ 95,\\ 90.25,\\ 85.74,\\ldots \\

\(\alpha\) 越接近 1,降温越慢,搜索通常越充分,但运行时间越长。

还可以使用:

线性降温:

\T_k=T_0-\\beta k \\

倒数降温:

\T_k=\\frac{T_0}{1+\\beta k} \\

对数降温:

\T_k=\\frac{C}{\\ln(k+1)} \\

理论上的全局收敛通常要求非常缓慢的降温,但工程实践更常使用几何降温。

4.6 初始温度的估计

如果希望一个典型较差变化 \(\Delta E\) 在初始阶段以概率 \(p_0\) 被接受,可以令:

\p_0=\\exp\\left(-\\frac{\\Delta E}{T_0}\\right) \\

整理得到:

\T_0=-\\frac{\\Delta E}{\\ln p_0} \\

例如,希望典型坏变化 \(\Delta E=10\) 以 80% 的概率被接受:

\T_0=-\\frac{10}{\\ln0.8}\\approx44.8 \\

实际中可以先随机采样若干邻域变化,统计正的 \(\Delta E\),再估计初始温度。

4.7 邻域与步长

模拟退火的效果高度依赖邻域设计。

连续变量可以采用:

\x'_j=x_j+\\epsilon_j \\

其中:

\\\epsilon_j\\sim N(0,\\sigma\^2) \\

高温时可以使用较大的 \(\sigma\),进行大范围探索;低温时逐渐减小 \(\sigma\),进行精细搜索。

排列问题可以使用交换、插入、逆序或 2-opt。

如果邻域太小,算法移动缓慢;如果邻域太大,候选解变化过于剧烈,低温时几乎不会被接受。

4.8 核心流程

text 复制代码
生成初始解 current
best = current
设置初始温度 T

while T 大于最低温度:

    在当前温度下重复若干次:

        candidate = neighbor(current)
        delta = f(candidate) - f(current)

        如果 delta <= 0:
            接受 candidate
        否则:
            p = exp(-delta / T)
            生成随机数 r

            如果 r < p:
                接受 candidate

        如果 current 优于 best:
            best = current

    T = alpha * T

输出 best

4.9 适用问题与局限

模拟退火适合连续、二进制、排列和调度问题,只要能够定义合理邻域。

优点:

  • 结构简单;
  • 内存占用低;
  • 能跳出局部最优;
  • 不要求目标函数可导;
  • 容易与局部搜索结合。

局限:

  • 对邻域和降温策略敏感;
  • 降温太快容易早熟;
  • 降温太慢运行时间长;
  • 通常是单解搜索;
  • 实际快速版本不能保证全局最优。

五、遗传算法

5.1 核心思想

遗传算法同时维护多个候选解,称为种群。

每个候选解称为个体,个体的表示称为染色体。算法让较好的个体获得更多繁殖机会,通过交叉重新组合已有信息,通过变异持续引入新信息。

遗传算法可以概括为:

保留优秀信息 + 重新组合信息 + 持续引入新信息

其中:

  • 选择负责保留;
  • 交叉负责组合;
  • 变异负责创新;
  • 精英保留防止最好解丢失;
  • 种群多样性保证搜索不会过早停止。

5.2 编码方式

遗传算法的第一步是确定染色体怎样表示一个实际解。

二进制编码:

text 复制代码
[1, 0, 1, 1, 0]

适合背包、特征选择和设备开关问题。

整数编码:

text 复制代码
[2, 1, 3, 2]

可以表示四个任务分别分配给哪台机器。

实数编码:

text 复制代码
[2.5, -1.2, 7.8]

适合连续参数优化。

排列编码:

text 复制代码
[3, 1, 5, 2, 4]

适合旅行商、排序和调度问题。

树形编码适合数学表达式、程序和规则优化。

混合编码适合同时含有布尔、整数、连续和排列变量的问题。

编码不只是存储格式,它决定交叉和变异能否产生有意义的新解。

5.3 初始种群

初始种群可以完全随机生成,也可以使用启发式方法生成部分优质个体。

完全随机的优点是多样性高,缺点是初始质量可能很差。

完全启发式的优点是起点好,缺点是个体之间可能过于相似。

实际中常使用混合初始化:

  • 一部分随机;
  • 一部分由贪心或领域规则生成。

5.4 适应度函数

适应度函数决定算法认为哪个个体更优秀。

对于最大化问题,可以直接使用:

\F(x)=f(x) \\

对于最小化问题,可以使用排序选择或锦标赛选择,不一定必须把目标函数转换成正数。

也可以使用:

\F(x)=\\frac{1}{f(x)+\\varepsilon} \\

但这种转换会改变适应度尺度,需要谨慎。

约束问题常使用惩罚函数:

\F(x)=f(x)+\\lambda V(x) \\

其中 \(V(x)\) 是约束违反程度。

适应度函数是遗传算法的方向盘。如果设计错误,算法会高效地优化错误目标。

5.5 选择操作

选择决定哪些个体有机会成为父代。

轮盘赌选择的概率为:

\p_i=\\frac{F_i}{\\sum_{j=1}\^{N}F_j} \\

适应度越高,选择概率越大。但轮盘赌对适应度数值尺度比较敏感。

锦标赛选择更常用。每次随机抽取 \(k\) 个个体,选择其中最好的一个。

当 \(k\) 较小时,随机性较强,多样性较好;当 \(k\) 较大时,优秀个体更容易胜出,收敛更快,但早熟风险更高。

排名选择先按适应度排序,再根据排名分配概率,能够减少极端个体对种群的垄断。

5.6 交叉操作

交叉将两个父代中的部分信息组合起来。

单点交叉示例:

text 复制代码
父代1:111|100
父代2:110|010

子代1:111|010
子代2:110|100

两点交叉交换中间片段。

均匀交叉对每个基因独立决定来自哪个父代。

实数编码可以使用算术交叉:

\c_1=\\alpha p_1+(1-\\alpha)p_2 \\

\c_2=(1-\\alpha)p_1+\\alpha p_2 \\

排列编码必须使用专用交叉,例如:

  • 顺序交叉 OX;
  • 部分映射交叉 PMX;
  • 循环交叉 CX。

普通单点交叉可能导致元素重复和缺失。

5.7 变异操作

二进制变异:

\0\\leftrightarrow1 \\

实数变异可以添加随机扰动:

\x'=x+\\epsilon \\

其中:

\\\epsilon\\sim N(0,\\sigma\^2) \\

排列变异可以使用交换、插入或逆序。

变异不是为了每次都改进解,而是为了引入种群中没有的新信息。

如果某个基因值已经从整个种群中消失,仅靠交叉无法重新产生它,变异可以恢复这种可能性。

5.8 精英保留

精英保留将当前最好个体直接复制到下一代。

这样通常可以保证:

\F_{\\text{best}}\^{(t+1)} \\geq F_{\\text{best}}\^{(t)} \\

但精英数量不能太多,否则种群会迅速同质化。

5.9 过早收敛

如果种群变成:

text 复制代码
110101
110101
110101
110101
110101

交叉几乎无法产生新结构,算法只能依赖少量变异继续搜索。

解决方法包括:

  • 增大种群;
  • 降低选择压力;
  • 减少精英数量;
  • 提高变异率;
  • 定期加入随机个体;
  • 使用多种群;
  • 根据多样性自适应调整参数;
  • 与局部搜索结合。

5.10 核心流程

text 复制代码
设计染色体编码
初始化种群 population
计算所有个体适应度

重复:

    保存少量精英

    while 新种群未满:
        选择两个父代
        按概率执行交叉
        对子代执行变异
        修复非法解或计算惩罚
        将子代加入新种群

    加入精英
    用新种群替换旧种群
    重新计算适应度

直到满足停止条件

输出历史最优个体

5.11 适用问题与局限

遗传算法适合:

  • 背包和选择问题;
  • 排列和路径问题;
  • 任务分配;
  • 连续参数优化;
  • 混合变量优化;
  • 特征选择;
  • 规则和程序结构优化。

优点是通用、易并行、能维护多个搜索区域。

局限是函数评价次数多、参数较多、结果有随机性、编码和算子设计需要问题知识。

六、粒子群算法

6.1 核心思想

粒子群算法同时维护多个粒子。每个粒子代表一个候选解,并拥有位置和速度。

粒子根据三种信息更新运动:

  1. 保留上一轮的运动趋势;
  2. 靠近自己的历史最好位置;
  3. 靠近群体发现的最好位置。

速度更新公式为:

\\\mathbf{v}_i\^{t+1} = \\omega\\mathbf{v}_i\^t + c_1r_1(\\mathbf{p}_i-\\mathbf{x}_i\^t) + c_2r_2(\\mathbf{g}-\\mathbf{x}_i\^t) \\

位置更新公式为:

\\\mathbf{x}_i\^{t+1} = \\mathbf{x}_i\^t+\\mathbf{v}_i\^{t+1} \\

其中:

  • \(\mathbf{x}_i\):当前位置;
  • \(\mathbf{v}_i\):速度;
  • \(\mathbf{p}_i\):个人历史最优位置;
  • \(\mathbf{g}\):群体历史最优位置;
  • \(\omega\):惯性权重;
  • \(c_1\):个体学习因子;
  • \(c_2\):社会学习因子;
  • \(r_1,r_2\):区间 \(0,1\) 上的随机数。

6.2 三个速度分量

惯性项:

\\\omega\\mathbf{v}_i\^t \\

它让粒子继续沿原来的方向运动。惯性较大时,搜索范围更广;惯性较小时,搜索更稳定。

个体认知项:

\c_1r_1(\\mathbf{p}_i-\\mathbf{x}_i\^t) \\

它让粒子回忆自己的成功经验。

社会项:

\c_2r_2(\\mathbf{g}-\\mathbf{x}_i\^t) \\

它让粒子学习群体中目前最成功的位置。

PSO 的核心就是:

保持原方向 + 学习自己的经验 + 学习群体的经验

6.3 一个简单数值例子

求:

\\\min f(x)=(x-3)\^2 \\

某粒子当前状态:

\x=8,\\qquad v=-1 \\

个人最优:

\p=6 \\

群体最优:

\g=4 \\

设置:

\\\omega=0.7,\\quad c_1=c_2=1.5 \\

随机数:

\r_1=0.4,\\quad r_2=0.6 \\

则:

\v' = 0.7(-1) + 1.5(0.4)(6-8) + 1.5(0.6)(4-8) \\

得到:

\v'=-5.5 \\

新位置:

\x'=8-5.5=2.5 \\

目标值从:

\f(8)=25 \\

下降到:

\f(2.5)=0.25 \\

6.4 参数作用

惯性权重 \(\omega\):

  • 大:探索能力强;
  • 小:局部搜索能力强。

常使用线性递减惯性:

\\\omega(t) = \\omega_{\\max} - \\frac{t}{T} (\\omega_{\\max}-\\omega_{\\min}) \\

例如从 \(0.9\) 逐渐减小到 \(0.4\)。

\(c_1\) 较大时,粒子更相信自己的经验;\(c_2\) 较大时,粒子更容易追随群体。

常见初始设置:

\c_1=c_2=1.5 \\

或接近 2 的数值。

6.5 速度限制与边界处理

为了防止粒子跨越搜索区域,可以限制:

\-v_{\\max}\\leq v_{ij}\\leq v_{\\max} \\

位置越界时,可以:

  • 截断到边界;
  • 反弹速度;
  • 重新随机采样;
  • 使用周期映射。

截断简单,但可能导致大量粒子堆积在边界。

6.6 全局拓扑与局部拓扑

gbest 模型中,所有粒子参考同一个全局最优位置,收敛快,但容易早熟。

lbest 模型中,每个粒子只参考邻域最好位置,信息传播较慢,但多样性通常更好。

6.7 核心流程

text 复制代码
随机初始化粒子位置和速度
计算目标函数
设置每个粒子的个人最优
找到群体全局最优

重复:

    对每个粒子:
        更新速度
        限制速度
        更新位置
        处理越界
        计算目标函数
        更新个人最优
        更新群体最优

直到达到停止条件

输出群体最优位置

6.8 适用问题与局限

标准 PSO 天然适合连续参数优化,例如:

  • 控制器参数;
  • 工程设计变量;
  • 模型超参数;
  • 连续资源分配;
  • 黑盒函数优化。

优点:

  • 结构简单;
  • 参数较少;
  • 前期收敛快;
  • 易于并行;
  • 不需要梯度。

局限:

  • 容易过早聚集;
  • 后期可能停滞;
  • 对变量尺度敏感;
  • 离散问题需要重新设计;
  • 不能保证全局最优。

七、蚁群算法

7.1 核心思想

蚁群算法通过信息素实现群体间接协作。

每只蚂蚁根据当前信息素和启发式信息逐步构造一个完整解。优秀解中的决策元素获得更多信息素,使后续蚂蚁更倾向于选择这些元素。

以旅行商问题为例,蚂蚁从城市 \(i\) 选择城市 \(j\) 的概率通常为:

\p_{ij} = \\frac{\\tau_{ij}\^{\\alpha}\\eta_{ij}\^{\\beta}} {\\sum_{k\\in A_i}\\tau_{ik}\^{\\alpha}\\eta_{ik}\^{\\beta}} \\

其中:

  • \(\tau_{ij}\):边 \((i,j)\) 上的信息素;
  • \(\eta_{ij}\):启发式信息;
  • \(A_i\):当前允许访问的城市集合;
  • \(\alpha\):信息素重要程度;
  • \(\beta\):启发式信息重要程度。

旅行商问题中常使用:

\\\eta_{ij}=\\frac{1}{d_{ij}} \\

距离越短,启发值越大。

7.2 信息素更新

完成一轮构造后,信息素通常进行挥发和增强:

\\\tau_{ij} \\leftarrow (1-\\rho)\\tau_{ij} + \\Delta\\tau_{ij} \\

其中 \(\rho\) 是挥发率。

挥发的作用是:

  • 减弱过时经验;
  • 防止信息素无限增长;
  • 保持探索能力;
  • 降低早期错误路径的长期影响。

优秀路径可以增加更多信息素,例如:

\\\Delta\\tau_{ij} = \\frac{Q}{L} \\

其中 \(L\) 是路线长度,路线越短,增量越大。

7.3 正反馈机制

蚁群算法的核心是正反馈:

text 复制代码
较好的路径
→ 获得更多信息素
→ 后续蚂蚁更容易选择
→ 更多蚂蚁构造相似好路径
→ 信息素进一步增强

正反馈能快速积累优秀经验,但也可能导致所有蚂蚁过早集中在一条次优路径上。

因此必须使用信息素挥发、随机选择和信息素上下限维持多样性。

7.4 构造式搜索

蚁群算法与粒子群不同。

粒子群中的每个粒子本身就是一个完整解,并在搜索空间中移动。

蚁群中的蚂蚁通常从空解开始,一步一步选择决策元素,直到构造出完整方案。

例如旅行商问题:

text 复制代码
起点 1
→ 选择城市 4
→ 选择城市 2
→ 选择城市 5
→ 选择城市 3
→ 返回起点

因此,蚁群算法特别适合可以逐步构造解的问题。

7.5 核心流程

text 复制代码
初始化所有信息素

重复:

    对每只蚂蚁:
        从初始状态出发
        根据概率规则逐步选择决策
        构造完整解
        计算解的质量

    所有信息素执行挥发
    根据优秀解增加信息素
    更新历史最好解

直到达到停止条件

输出历史最好解

7.6 关键参数

\(\alpha\) 控制信息素的重要程度。过大时,算法过度依赖历史路径。

\(\beta\) 控制启发式信息的重要程度。过大时,算法可能接近贪心策略。

\(\rho\) 控制挥发速度。挥发太慢容易早熟,挥发太快则经验难以积累。

蚂蚁数量影响每轮搜索覆盖范围和计算量。

7.7 适用问题与局限

蚁群算法适合:

  • 旅行商问题;
  • 车辆路径规划;
  • 网络路由;
  • 作业调度;
  • 排序;
  • 组合构造问题。

优点:

  • 适合图和路径;
  • 能利用启发式信息;
  • 群体并行;
  • 信息素具有长期记忆;
  • 容易与局部搜索结合。

局限:

  • 前期收敛可能较慢;
  • 参数之间相互影响;
  • 信息素容易过早集中;
  • 连续问题需要专门改造;
  • 大规模问题的信息素矩阵可能占用较多资源。

八、禁忌搜索

8.1 核心思想

禁忌搜索也是单解局部搜索,但它增加了记忆机制。

普通局部搜索容易在几个解之间循环。例如:

text 复制代码
A → B → A → B

禁忌搜索记录近期执行过的动作,在一段时间内禁止反向操作,从而迫使搜索进入新的区域。

它通常允许当前解变差,但不会随机接受,而是选择当前允许候选中最好的一个。

8.2 禁忌对象

禁忌表可以记录:

  • 最近访问过的解;
  • 最近执行的交换;
  • 最近改变的变量;
  • 最近添加或删除的边;
  • 某种解属性。

直接记录完整解可能占用较多内存,因此实践中常记录"动作"或"属性"。

例如旅行商问题中,刚刚交换了城市 2 和城市 5,可以暂时禁止立即把它们交换回来。

8.3 禁忌期限

禁忌期限表示一个动作被禁止多少轮。

期限太短,算法容易循环;期限太长,又会禁止过多有价值的移动。

禁忌期限可以固定,也可以在一定范围内随机变化。

8.4 特赦准则

禁忌不是绝对禁止。

如果某个禁忌动作能产生比历史最优解更好的结果,可以允许它执行。这称为特赦准则。

最常见的特赦条件是:

\f(x')\

对于最小化问题,只要候选解优于历史最优,即使动作处于禁忌状态,也可以接受。

8.5 核心流程

text 复制代码
生成初始解 current
best = current
初始化禁忌表

重复:

    生成 current 的多个邻域解

    对每个候选解:
        判断对应动作是否被禁忌
        如果被禁忌,检查是否满足特赦条件

    从允许候选中选择最好的一个
    current = 该候选解

    如果 current 优于 best:
        best = current

    将本次动作加入禁忌表
    更新禁忌期限
    删除过期记录

直到达到停止条件

输出 best

8.6 强化与多样化

禁忌搜索不仅可以使用短期记忆,还可以使用长期记忆。

强化策略让搜索更多关注历史上表现较好的区域。

多样化策略鼓励搜索较少访问的区域,例如惩罚经常出现的边或变量组合。

8.7 适用问题与局限

适合:

  • 生产调度;
  • 车辆路径;
  • 排课;
  • 任务分配;
  • 设施选址;
  • 组合优化。

优点:

  • 能主动避免循环;
  • 允许移动到较差解;
  • 可结合长期记忆;
  • 对组合问题效果常较稳定。

局限:

  • 禁忌对象和期限需要设计;
  • 每轮通常要评估多个邻域解;
  • 大邻域下计算量较大;
  • 缺少随机性时可能搜索范围有限。

九、变邻域搜索

9.1 核心思想

一个解可能在某种邻域下是局部最优,但在另一种邻域下仍有改进空间。

例如一条路线通过交换任意两个城市都无法继续缩短,但通过反转一段路线可能得到明显改进。

变邻域搜索不固定使用一个邻域,而是在多个邻域之间系统切换。

9.2 邻域集合

以旅行商问题为例,可以设计:

  • \(N_1\):交换两个城市;
  • \(N_2\):插入一个城市;
  • \(N_3\):反转一段路线;
  • \(N_4\):2-opt;
  • \(N_5\):3-opt。

邻域通常从小到大排列。

小邻域适合精细优化,大邻域适合结构性跳跃。

9.3 扰动与局部搜索

经典变邻域搜索通常包含两个阶段。

扰动阶段:从当前解的第 \(k\) 个邻域中随机产生一个新解。

局部搜索阶段:从新解出发执行局部搜索,直到达到局部最优。

如果得到更好的解,就接受并重新从第一个邻域开始;否则尝试下一个邻域。

9.4 核心流程

text 复制代码
准备邻域 N1, N2, ..., Nk
生成初始解 current

重复:

    neighborhood_index = 1

    while neighborhood_index <= k:

        candidate = 在对应邻域中扰动 current
        candidate = 对 candidate 执行局部搜索

        如果 candidate 优于 current:
            current = candidate
            neighborhood_index = 1
        否则:
            neighborhood_index += 1

直到满足停止条件

输出 current

9.5 为什么有效

局部最优是相对于邻域定义的。

如果:

\f(x)\\leq f(x'),\\qquad \\forall x'\\in N_1(x) \\

只能说明 \(x\) 在 \(N_1\) 下局部最优,不能说明它在 \(N_2\)、\(N_3\) 下也局部最优。

切换邻域相当于改变了"附近"的定义,因此能够突破单一邻域的限制。

9.6 适用问题与局限

适合:

  • 路线规划;
  • 调度;
  • 设施选址;
  • 分配问题;
  • 聚类;
  • 组合优化。

优点:

  • 思想直观;
  • 易结合问题知识;
  • 通常比单一邻域稳定;
  • 容易与其他算法混合。

局限:

  • 需要设计多个高质量邻域;
  • 邻域顺序会影响效果;
  • 大邻域可能计算昂贵;
  • 如果所有邻域都过于相似,改进有限。

十、七种算法的本质对比

算法 搜索单位 新解产生方式 主要记忆 跳出局部最优方式 典型问题
局部搜索 单个解 邻域移动 当前解 通常没有 快速局部改进
模拟退火 单个解 随机邻域 当前解、历史最优 概率接受较差解 连续、排列、调度
遗传算法 种群 选择、交叉、变异 种群和精英 多样性、变异 通用、混合变量
粒子群 粒子群 更新速度和位置 pbest、gbest 惯性和随机项 连续参数优化
蚁群算法 多只构造个体 概率构造完整解 信息素 挥发和随机选路 路径、排序、组合
禁忌搜索 单个解 邻域移动 禁忌表 禁止返回、允许劣化 路径、调度、分配
变邻域搜索 单个解 多种邻域切换 当前解 改变邻域结构 组合优化

十一、同一个问题为什么可以用多种算法

以旅行商问题为例:

  • 局部搜索:不断做 2-opt;
  • 模拟退火:对路线交换或逆序,并概率接受更差路线;
  • 遗传算法:把路线作为排列染色体,执行顺序交叉和逆序变异;
  • 蚁群算法:根据边的信息素逐步构造路线;
  • 禁忌搜索:选择最好的交换动作,并禁止立即撤销;
  • 变邻域搜索:在交换、插入、逆序和 2-opt 之间切换;
  • 粒子群:需要设计离散速度或交换序列,通常不如前几种自然。

同一个问题能用多种算法,不代表它们效果一样。真正决定效果的是:

  • 解的表示是否自然;
  • 新解操作是否保留问题结构;
  • 约束处理是否合理;
  • 搜索参数是否匹配;
  • 是否结合了有效的局部搜索;
  • 函数评价成本;
  • 问题规模和实例特征。

十二、怎样根据问题选择算法

12.1 连续参数优化

优先考虑:

  • 粒子群;
  • 模拟退火;
  • 实数遗传算法;
  • 差分进化;
  • 进化策略。

如果目标函数平滑且可导,应优先考虑梯度法,而不是直接使用启发式算法。

12.2 二进制选择问题

例如背包、特征选择和开关配置,优先考虑:

  • 二进制遗传算法;
  • 模拟退火;
  • 禁忌搜索;
  • 二进制粒子群;
  • 贪心加局部搜索。

12.3 排列与路径问题

优先考虑:

  • 蚁群算法;
  • 模拟退火;
  • 禁忌搜索;
  • 变邻域搜索;
  • 排列遗传算法;
  • 2-opt、3-opt。

12.4 复杂调度问题

常用:

  • 遗传算法;
  • 禁忌搜索;
  • 变邻域搜索;
  • 模拟退火;
  • 混合算法。

12.5 混合变量问题

如果问题同时包含是否选择、机器编号、连续参数和顺序,可以考虑:

  • 混合编码遗传算法;
  • 分层优化;
  • 不同算法分别负责不同变量;
  • 大邻域搜索;
  • 混合元启发式。

十三、约束条件的通用处理方法

实际问题往往有约束:

\g_i(x)\\leq0 \\

以及:

\h_j(x)=0 \\

常见方法如下。

13.1 惩罚函数

对于最小化问题:

\F(x)=f(x)+\\lambda V(x) \\

其中 \(V(x)\) 表示违反约束的程度。

优点是实现简单,缺点是惩罚系数难以设置。

13.2 修复方法

生成非法解后,把它修复成合法解。

例如背包超重时,不断移除价值重量比较低的物品。

13.3 可行编码

从编码或邻域设计上保证所有候选解天然满足约束。

例如旅行商问题使用排列编码,并使用保持排列合法的交叉与变异。

13.4 可行解优先

比较两个解时:

  1. 可行解优于不可行解;
  2. 两个都可行时,目标值更好的获胜;
  3. 两个都不可行时,违反约束更少的获胜。

这种方式可以减少对惩罚系数的依赖。

十四、停止条件与实验设计

常见停止条件:

  • 达到最大迭代次数;
  • 达到最大函数评价次数;
  • 运行时间达到上限;
  • 历史最优连续若干代没有改善;
  • 种群多样性低于阈值;
  • 找到满足目标要求的解。

对于随机算法,不能只运行一次。

应使用多个随机种子独立运行,并记录:

  • 最好值;
  • 最差值;
  • 平均值;
  • 标准差;
  • 中位数;
  • 运行时间;
  • 函数评价次数;
  • 达到目标质量的成功率。

比较算法时,应保证相同的计算预算,例如相同函数评价次数,而不是只比较迭代次数。

十五、常见错误

15.1 只关注算法名称

很多初学者花大量时间比较"鲸鱼""灰狼""萤火虫"等名称,却忽略了解表示和搜索算子。

应先判断算法如何产生新解、如何记录经验、如何维持多样性。

15.2 不设置基线

至少应与以下方法比较:

  • 随机搜索;
  • 贪心算法;
  • 简单局部搜索;
  • 问题专用算法。

如果复杂算法不能明显超过简单基线,就需要重新检查设计。

15.3 只报告一次最好结果

随机算法偶尔找到好解并不代表稳定有效。应报告多次实验统计结果。

15.4 参数调得太多

参数越多,越容易在测试集上过度调优。应先使用合理默认值,再进行系统实验。

15.5 忽略变量尺度

连续变量范围差异很大时,应考虑归一化,否则某些变量可能主导搜索。

15.6 邻域或编码不合法

排列问题使用普通交叉,可能产生重复元素;约束问题不修复非法解,可能浪费大量计算。

15.7 当前解与历史最优混淆

模拟退火、粒子群和其他算法中,当前状态与历史最好状态必须分开维护。

十六、混合算法为什么常常更好

单一算法通常有明显偏向。

遗传算法擅长全局组合,但后期局部精细搜索可能较弱。

局部搜索收敛快,但容易陷入局部最优。

因此可以组合:

text 复制代码
遗传算法负责全局探索
+
局部搜索负责精细改进

这种方法常称为模因算法。

其他常见组合:

  • 粒子群 + 局部搜索;
  • 蚁群 + 2-opt;
  • 模拟退火 + 变邻域;
  • 禁忌搜索 + 大邻域;
  • 遗传算法优化蚁群参数;
  • 蚁群构造路线,粒子群优化连续参数。

混合算法不是简单叠加多个名称,而是让不同模块承担互补职责。

十七、推荐学习顺序

推荐顺序:

text 复制代码
局部搜索
→ 模拟退火
→ 遗传算法
→ 粒子群算法
→ 蚁群算法
→ 禁忌搜索
→ 变邻域搜索

局部搜索帮助理解邻域和局部最优。

模拟退火帮助理解为什么需要接受较差解。

遗传算法帮助理解种群、编码、交叉、变异和多样性。

粒子群帮助理解个人经验与群体经验。

蚁群帮助理解构造式搜索和间接通信。

禁忌搜索帮助理解记忆机制。

变邻域搜索帮助理解邻域定义本身就是搜索策略。

十八、推荐练习路线

18.1 背包问题

可以练习:

  • 二进制编码;
  • 适应度函数;
  • 约束惩罚;
  • 遗传算法;
  • 模拟退火;
  • 禁忌搜索。

18.2 旅行商问题

可以练习:

  • 排列编码;
  • 交换、插入和逆序邻域;
  • 2-opt;
  • 模拟退火;
  • 遗传算法;
  • 蚁群算法;
  • 禁忌搜索;
  • 变邻域搜索。

18.3 多峰连续函数

可以练习:

  • 粒子群;
  • 模拟退火;
  • 实数遗传算法;
  • 探索与利用;
  • 参数敏感性;
  • 多次运行统计。

常见测试函数包括 Sphere、Rastrigin、Rosenbrock 和 Ackley。

十九、学习每种算法时应该回答的问题

学习一种算法时,建议依次回答:

  1. 一个解如何表示?
  2. 算法维护一个解还是多个解?
  3. 新解怎样产生?
  4. 什么信息会被保留?
  5. 如何利用历史好解?
  6. 如何探索新区域?
  7. 如何跳出局部最优?
  8. 如何处理边界和约束?
  9. 关键参数分别控制什么?
  10. 如何判断算法是否早熟或停滞?
  11. 应该与什么基线比较?
  12. 算法最适合哪类问题?
  13. 在什么情况下不应该使用它?

如果这些问题能够清楚回答,就不仅是记住了算法流程,而是真正理解了算法。

二十、最终总结

启发式算法的共同目标是在无法穷举或精确求解的情况下,以有限计算资源寻找较好的近似解。

局部搜索从邻域中寻找改进,是很多算法的基础。

模拟退火通过温度控制接受较差解的概率,从而跳出局部最优。

遗传算法通过选择、交叉和变异,让种群中的优秀信息不断被保留、重组和创新。

粒子群通过惯性、个人最优和群体最优共同更新粒子运动,特别适合连续参数优化。

蚁群算法通过信息素和概率构造解,特别适合路径和组合问题。

禁忌搜索利用记忆机制避免循环,并允许搜索经过暂时较差的区域。

变邻域搜索通过系统切换邻域,突破单一邻域下的局部最优。

这些算法表面形式不同,但真正的核心始终是:

怎样表示解、怎样产生新解、怎样保存经验、怎样平衡探索与利用。

在实际问题中,算法名称通常不是最重要的。更重要的是问题建模、编码方式、邻域设计、约束处理和实验验证。一个简单算法配合优秀的问题设计,往往比一个复杂算法直接套用更有效。