Skip to content
Scroll to top↑

遗传算法求解迷宫

遗传算法(Genetic Algorithm,GA)是模仿自然选择与遗传机制的一种搜索启发式算法。下面用它来解一个随机迷宫:种群里的每个个体是一串移动指令(上/下/左/右),适应度是个体在迷宫中"走到底"后离终点的距离——离得越近,越有资格繁衍后代,经过一代代的选择、交叉与变异,种群逐渐学会穿越迷宫。

最优路径 种群云 最优适应度
世代 0最优 平均 0探索中…

算法细节

由 DeepSeek-V4-Flash 实现。

编码:移动序列

每个个体是一条固定长度的指令串,指令为四个方向之一:

指令集    0=上   1=下   2=左   3=右
个体      3 3 0 2 3 1 0 0 1 2 ...   (长度 ~8×迷宫边长)

把个体放进迷宫执行:从起点出发按指令依次移动,撞墙或越界的指令被忽略、原地不动。这样基因里"多余的"指令不会破坏路径,只会暂时浪费一格,算法可以容忍冗余基因的存在。

适应度

ts
到达终点 ? 1e6 - 已走步数     // 巨大加成,且偏好更短路径
         : 已走步数 * 0.1 + 1000 * (1 - 距终点曼哈顿距离 / 最大距离)
  • 没到达前,离终点的距离主导适应度,推动路径朝终点延伸;
  • 已走的步数作次要奖励,让能走得更远的个体在"同距离"下胜出;
  • 一旦到达终点,适应度跃升到 106 量级,之后选择压力转为 路径更短更好——所以你会在求解后看到路径还在一点点缩短。

选择、交叉与变异

算子做法
选择锦标赛选择(tournament):随机抽 3 个个体,取适应度最高者作为父本,重复两次得到父母
交叉单点交叉:随机选一个切点,把父亲切点后的指令段与母亲的后半段拼接,得到"父前半 + 母后半"的子代
变异逐基因变异:每个方向指令以变异率(默认 6%)随机改成一个新方向
精英保留当前最优个体原封不动进入下一代,保证适应度不倒退
多样性每代有 2% 概率注入一个全新随机个体,防止过早收敛

evolve():一代是如何产生的

evolve 是算法的核心,接收上一代(pop)、上一代每个个体的适应度(fits)与配置,产出一模一样大小的下一代:

ts
export function evolve(pop, fits, cfg, rand = Math.random): number[][] {
  const next: number[][] = []
  const { chromLen: L, crossoverRate: pc, mutationRate: pm } = cfg

  // 1. 精英保留:当前最优个体原封不动进入下一代
  let bi = 0
  for (let i = 0; i < fits.length; i++) if (fits[i] > fits[bi]) bi = i
  next.push([...pop[bi]])

  // 2. 循环补满下一代
  while (next.length < pop.length) {
    // 3. 随机注入新个体,防止过早收敛
    if (rand() < 0.02) { next.push(randomChromosome(L, rand)); continue }

    // 4. 选亲本:两次锦标赛各抽 3 个候选,取其中适应度最高者
    const child = [...pop[tournament(fits, 3, rand)]]   // 父本(复制)
    const mate = pop[tournament(fits, 3, rand)]         // 母本(只读)

    // 5. 单点交叉:按概率 0.7,把父本切点后的段换成母本的
    if (rand() < pc) {
      const pt = (rand() * L) | 0
      for (let i = pt; i < L; i++) child[i] = mate[i]
    }
    // 6. 逐基因变异:每位指令以概率 pm 随机改成新方向
    for (let i = 0; i < L; i++) {
      if (rand() < pm) child[i] = (rand() * 4) | 0
    }
    next.push(child)
  }
  return next
}

逐步解读:

① 精英保留 —— 找到适应度最高的个体,直接复制一份放进下一代。三点值得注意:

  • 它保证"迄今为止最好的路径"不会被交叉/变异破坏,代际最优适应度单调不减
  • 若没有它,好不容易进化出来的好个体可能刚出现就被下一代弄丢了,前功尽弃;

② 循环条件 —— while (next.length < pop.length) 保证下一代与上一代人口数完全一致

③ 随机注入 —— 2% 概率塞入一个全新的随机染色体并跳过交叉/变异。这是对抗早熟收敛的保险:当种群被交叉压到大家几乎一样时,近亲杂交产不出新花样,只能靠外来基因打破僵局。

④ 选亲本(锦标赛选择) —— tournament 随机抽 3 个候选、返回其中适应度最高者的下标:

ts
function tournament(fits, k, rand) {
  let best = 0, bestFit = -Infinity
  for (let i = 0; i < k; i++) {
    const j = (rand() * fits.length) | 0
    if (fits[j] > bestFit) { bestFit = fits[j]; best = j }
  }
  return best
}

它让强个体有很高的概率成为父母、弱个体偶尔也能胜出——k 就是取舍的旋钮:k=1 等于随机抽(无选择压力),k 越大越"择优"。这里 k=3 兼顾利用与探索。父本用 [...] 复制(因为之后要改),母本只读不复制。

⑤ 单点交叉 —— 按概率 0.7(crossoverRate),随机选一个切点 pt,把父本的后半段用母本的同位段覆盖,得到"父前半 + 母后半"的子代。交叉是 GA 的组合引擎:两条路径里各自的好片段(比如一段漂亮的转向序列)可能被拼接成更完整的走廊。剩下 30% 的概率跳过交叉,子代直接是父本的克隆,交给变异去扰动。

⑥ 逐基因变异 —— 对 L 位指令逐位以概率 mutationRate(默认 6%)随机改成 0~3 之一。变异是探索的头号来源:哪怕父母都没试过的某个拐弯,子代也能靠它撞出来;同时大多数子代只是父母路径的轻微扰动——探索发生在已知好解的邻近区域。

关键细节

  • 迷宫保证可解:随机抛墙后用 BFS 校验起点到终点是否连通,不连通就重新生成。
  • 撞墙即忽略 是这类"指令串"编码的经典处理:它把可行解空间变得非常宽容,配合同距离下按步数排序,种群很容易从"只顾往前冲"演化出"会拐弯"的路径。
  • 适应度曲线用对数纵轴绘制,因为到达终点后适应度从千级跳到百万级,线性坐标会压得前面看不出变化。

一个有趣的观察:把变异率拉到 20% 并缩小种群,你会看到种群像一群"醉酒的人"在迷宫里乱撞,难以收敛;而变异率过低时,种群又容易在某个局部路径上固化,找不到更近的捷径。平衡探索与利用,正是遗传算法的核心课题。

源代码见 genetic-maze/index.vueuseGeneticMaze.ts