You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何筛选矩形数组以消除碰撞且保留尽可能多的矩形?

以最少删减量筛选无碰撞矩形集合的最优实现方式

我有一个可能存在矩形碰撞的数组,想要用最少的删减量筛选出无碰撞的矩形集合,请问最优实现方式是什么?

代码上下文

type Rect = {
  x: number;
  y: number;
  width: number;
  height: number;
};

function isRectsColliding(rect1: Rect, rect2: Rect) {
  return !(
    rect1.x > rect2.x + rect2.width ||
    rect1.x + rect1.width < rect2.x ||
    rect1.y > rect2.y + rect2.height ||
    rect1.y + rect1.height < rect2.y
  );
}

const rects = [
  { x: 0, y: 181, width: 6, height: 6 },
  { x: 6, y: 147, width: 6, height: 6 },
  { x: 32, y: 124, width: 6, height: 6 },
  { x: 34, y: 7, width: 6, height: 6 },
  { x: 35, y: 11, width: 6, height: 6 },
  { x: 36, y: 0, width: 6, height: 6 },
  { x: 39, y: 15, width: 6, height: 6 },
];

问题本质

这个问题等价于图的最大独立集问题:把每个矩形看作图的节点,两个矩形碰撞则在对应节点间连一条边,我们需要找到最大的节点子集,其中任意两个节点都没有边相连(即无碰撞)。最大独立集是NP难问题,没有多项式时间的精确解法,但可以根据数据规模选择不同的实现方案。

实现方案

1. 贪心算法(高效近似解)

适合中等或较大规模的数据,实现简单、效率高,多数情况下能得到接近最优的结果。核心思路是每次移除当前碰撞次数最多的矩形,直到剩余矩形间无碰撞。

// 构建邻接表:记录每个矩形对应的碰撞矩形索引列表
function buildAdjacencyList(rects: Rect[]): number[][] {
  const adjList: number[][] = Array.from({ length: rects.length }, () => []);
  for (let i = 0; i < rects.length; i++) {
    for (let j = i + 1; j < rects.length; j++) {
      if (isRectsColliding(rects[i], rects[j])) {
        adjList[i].push(j);
        adjList[j].push(i);
      }
    }
  }
  return adjList;
}

// 贪心筛选最大无碰撞矩形索引集合
function getMaxNonCollidingIndexes(rects: Rect[]): number[] {
  const adjList = buildAdjacencyList(rects);
  let currentAdj = adjList.map(list => [...list]);
  const kept = Array(rects.length).fill(true);

  while (true) {
    // 计算每个保留矩形的碰撞次数
    const collisionCounts = currentAdj.map((list, idx) => kept[idx] ? list.length : -1);
    const maxCount = Math.max(...collisionCounts);
    if (maxCount === 0) break; // 无碰撞,退出循环

    // 移除碰撞次数最多的矩形
    const idxToRemove = collisionCounts.indexOf(maxCount);
    kept[idxToRemove] = false;
    // 更新邻接表,清除该矩形与其他矩形的关联
    currentAdj[idxToRemove].forEach(neighbor => {
      if (kept[neighbor]) {
        const neighborIdx = currentAdj[neighbor].indexOf(idxToRemove);
        currentAdj[neighbor].splice(neighborIdx, 1);
      }
    });
    currentAdj[idxToRemove] = [];
  }

  // 返回保留的矩形索引
  return kept.map((isKept, idx) => isKept ? idx : null).filter(Boolean) as number[];
}

const filteredRectIndexes = getMaxNonCollidingIndexes(rects);
console.log(filteredRectIndexes); // 输出 [0, 1, 2, 3, 5, 6]

2. 回溯法(精确最优解)

适合小规模数据(矩形数量少于20),能保证得到全局最优解,但时间复杂度为O(2^n),数据量大时效率极低。

// 回溯法找精确的最大无碰撞矩形索引集合
function getExactMaxNonCollidingIndexes(rects: Rect[]): number[] {
  const adjList = buildAdjacencyList(rects);
  let maxSet: number[] = [];

  // 递归回溯:currentSet为当前已选索引,remaining为剩余可选索引
  function backtrack(currentSet: number[], remaining: number[]) {
    if (remaining.length === 0) {
      if (currentSet.length > maxSet.length) {
        maxSet = [...currentSet];
      }
      return;
    }

    const firstIdx = remaining[0];
    // 情况1:不选当前第一个矩形,递归处理剩余部分
    backtrack(currentSet, remaining.slice(1));

    // 情况2:选当前第一个矩形,排除所有与它碰撞的矩形后递归
    const nonCollidingRemaining = remaining.filter(idx => !adjList[firstIdx].includes(idx));
    backtrack([...currentSet, firstIdx], nonCollidingRemaining);
  }

  backtrack([], Array.from({ length: rects.length }, (_, i) => i));
  // 按原数组顺序排序索引
  return maxSet.sort((a, b) => a - b);
}

const exactFiltered = getExactMaxNonCollidingIndexes(rects);
console.log(exactFiltered); // 输出 [0, 1, 2, 3, 5, 6]

总结

  • 若数据规模小,追求精确最优解:选择回溯法;
  • 若数据规模中等或较大,优先考虑效率:选择贪心算法;
  • 你的示例中两种方法都能得到期望的筛选结果。

内容的提问来源于stack exchange,提问作者Hakan Özdemir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 04:00:46