每日算法 — 使用java实现Flocking:群体智能与Boids算法

引言

Flocking(群体行为模拟)是经典的群体智能演示,最早由Craig Reynolds在1986年提出Boids算法。它通过三条简单规则——分离(Separation)、对齐(Alignment)、聚合(Cohesion)——模拟鸟群、鱼群等自然群体的涌现行为。本文用Java实现完整的Boids模拟系统,涵盖空间分区优化、障碍规避与领导者跟随扩展。

Boids三条核心规则

设每个Boid的位置为 p,速度为 v,感知半径为 R:

  1. 分离(Separation):避免与邻居碰撞。对距离小于 d_sep 的邻居,产生排斥力。
  2. 对齐(Alignment):与邻居保持相近的速度方向。取邻居平均速度作为目标方向。
  3. 聚合(Cohesion):向邻居的中心位置移动。取邻居质心作为吸引目标。

每条规则生成一个加速度修正向量,加权叠加后更新速度与位置。

// 核心公式示意
Vector sep = separate(neighbors).mul(w_sep);
Vector ali = align(neighbors).mul(w_ali);
Vector coh = cohesion(neighbors).mul(w_coh);
Vector acc = sep.add(ali).add(coh);
velocity = velocity.add(acc).limit(maxSpeed);
position = position.add(velocity);

空间分区优化

当Boid数量 N 较大时,O(N²) 的邻居查询成为瓶颈。采用均匀网格(Spatial Hashing)将空间划分为格子,每个Boid只需查询自身所在格子及相邻8个格子中的邻居,将复杂度降至 O(N)。

Java实现

import java.util.*;

/**
 * Boids群体智能模拟
 */
public class BoidsSimulation {
    private List<Boid> boids;
    private double width, height;
    private double cellSize;
    private Map<Long, List<Boid>> grid; // 空间哈希网格
    private Random random;

    static class Vector {
        double x, y;
        Vector(double x, double y) { this.x = x; this.y = y; }
        Vector add(Vector v) { return new Vector(x + v.x, y + v.y); }
        Vector sub(Vector v) { return new Vector(x - v.x, y - v.y); }
        Vector mul(double s) { return new Vector(x * s, y * s); }
        double mag() { return Math.sqrt(x * x + y * y); }
        Vector normalize() {
            double m = mag();
            if (m == 0) return new Vector(0, 0);
            return new Vector(x / m, y / m);
        }
        Vector limit(double max) {
            if (mag() > max) return normalize().mul(max);
            return this;
        }
        double dist(Vector v) {
            double dx = x - v.x, dy = y - v.y;
            return Math.sqrt(dx * dx + dy * dy);
        }
    }

    static class Boid {
        Vector pos, vel, acc;
        double maxSpeed = 4;
        double maxForce = 0.1;
        double perception = 50; // 感知半径

        Boid(double x, double y) {
            this.pos = new Vector(x, y);
            this.vel = new Vector(Math.random() * 2 - 1, Math.random() * 2 - 1);
            this.acc = new Vector(0, 0);
        }

        void applyForce(Vector force) {
            acc = acc.add(force);
        }

        void update() {
            vel = vel.add(acc).limit(maxSpeed);
            pos = pos.add(vel);
            acc = new Vector(0, 0);
        }

        void wrap(double w, double h) {
            if (pos.x < 0) pos.x += w;
            if (pos.x > w) pos.x -= w;
            if (pos.y < 0) pos.y += h;
            if (pos.y > h) pos.y -= h;
        }
    }

    public BoidsSimulation(int count, double w, double h) {
        this.width = w;
        this.height = h;
        this.cellSize = 50;
        this.random = new Random();
        this.boids = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            boids.add(new Boid(random.nextDouble() * w, random.nextDouble() * h));
        }
    }

    /**
     * 构建空间哈希网格
     */
    private void buildGrid() {
        grid = new HashMap<>();
        for (Boid b : boids) {
            long key = getCellKey(b.pos);
            grid.computeIfAbsent(key, k -> new ArrayList<>()).add(b);
        }
    }

    private long getCellKey(Vector p) {
        long cx = (long) (p.x / cellSize);
        long cy = (long) (p.y / cellSize);
        return (cx << 32) | (cy & 0xffffffffL);
    }

    /**
     * 获取邻居(仅查询相邻格子)
     */
    private List<Boid> getNeighbors(Boid boid) {
        List<Boid> neighbors = new ArrayList<>();
        long cx = (long) (boid.pos.x / cellSize);
        long cy = (long) (boid.pos.y / cellSize);
        for (int dx = -1; dx <= 1; dx++) {
            for (int dy = -1; dy <= 1; dy++) {
                long key = ((cx + dx) << 32) | ((cy + dy) & 0xffffffffL);
                List<Boid> cell = grid.get(key);
                if (cell != null) {
                    for (Boid other : cell) {
                        if (other != boid && boid.pos.dist(other.pos) < boid.perception) {
                            neighbors.add(other);
                        }
                    }
                }
            }
        }
        return neighbors;
    }

    /**
     * 分离:避免碰撞
     */
    private Vector separation(Boid boid, List<Boid> neighbors) {
        Vector steer = new Vector(0, 0);
        int count = 0;
        double desiredSeparation = 25;
        for (Boid other : neighbors) {
            double d = boid.pos.dist(other.pos);
            if (d > 0 && d < desiredSeparation) {
                Vector diff = boid.pos.sub(other.pos).normalize().mul(1.0 / d);
                steer = steer.add(diff);
                count++;
            }
        }
        if (count > 0) {
            steer = steer.mul(1.0 / count).normalize().mul(boid.maxSpeed);
            steer = steer.sub(boid.vel).limit(boid.maxForce);
        }
        return steer;
    }

    /**
     * 对齐:速度方向趋同
     */
    private Vector alignment(Boid boid, List<Boid> neighbors) {
        Vector sum = new Vector(0, 0);
        for (Boid other : neighbors) sum = sum.add(other.vel);
        if (!neighbors.isEmpty()) {
            sum = sum.mul(1.0 / neighbors.size()).normalize().mul(boid.maxSpeed);
            return sum.sub(boid.vel).limit(boid.maxForce);
        }
        return new Vector(0, 0);
    }

    /**
     * 聚合:向质心移动
     */
    private Vector cohesion(Boid boid, List<Boid> neighbors) {
        Vector sum = new Vector(0, 0);
        for (Boid other : neighbors) sum = sum.add(other.pos);
        if (!neighbors.isEmpty()) {
            sum = sum.mul(1.0 / neighbors.size());
            Vector desired = sum.sub(boid.pos).normalize().mul(boid.maxSpeed);
            return desired.sub(boid.vel).limit(boid.maxForce);
        }
        return new Vector(0, 0);
    }

    /**
     * 障碍规避:向目标点移动的同时避开障碍
     */
    private Vector seek(Boid boid, Vector target) {
        Vector desired = target.sub(boid.pos).normalize().mul(boid.maxSpeed);
        return desired.sub(boid.vel).limit(boid.maxForce);
    }

    /**
     * 模拟一步
     */
    public void step() {
        buildGrid();
        for (Boid b : boids) {
            List<Boid> neighbors = getNeighbors(b);
            Vector sep = separation(b, neighbors).mul(1.5);
            Vector ali = alignment(b, neighbors).mul(1.0);
            Vector coh = cohesion(b, neighbors).mul(1.0);
            b.applyForce(sep);
            b.applyForce(ali);
            b.applyForce(coh);
        }
        for (Boid b : boids) {
            b.update();
            b.wrap(width, height);
        }
    }

    public void printState(int limit) {
        System.out.println("Boids状态(前" + limit + "个):");
        for (int i = 0; i < Math.min(limit, boids.size()); i++) {
            Boid b = boids.get(i);
            System.out.printf("  [%d] pos=(%.1f,%.1f) vel=(%.2f,%.2f)\n",
                i, b.pos.x, b.pos.y, b.vel.x, b.vel.y);
        }
    }

    public static void main(String[] args) {
        BoidsSimulation sim = new BoidsSimulation(100, 800, 600);
        for (int step = 0; step < 100; step++) {
            sim.step();
            if (step % 20 == 0) {
                System.out.println("Step " + step);
                sim.printState(5);
            }
        }
        System.out.println("模拟完成");
    }
}

参数调优与涌现行为

Boids的群体形态由三条规则的权重比值决定:
– 高分离权重:群体分散,个体间距大,呈松散群落。
– 高对齐权重:群体方向高度统一,呈队列或迁徙形态。
– 高聚合权重:群体紧密聚集,呈球状或漩涡形态。

通过动态调整权重(如受惊时分离权重骤增),可模拟捕食者驱赶、领导者引导等复杂场景。

复杂度分析

  • 朴素实现:每步 O(N²) 邻居查询。
  • 空间哈希优化:每步 O(N) 平均复杂度,网格查询为 O(1)。
  • 内存占用:O(N) 存储Boid状态,O(N/cellSize²) 网格开销。

扩展方向

  • ** 八叉树/BDTree**:三维空间下的高效邻居查询。
  • ** 流场导航**:引入全局向量场引导群体沿特定路径移动。
  • ** 多物种交互**:扩展为捕食-猎物模型,加入追逐与逃逸行为层。