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

最优查找网格中可放置指定矩形的最近空闲位置

嗨,这个问题我在做UI布局和网格资源分配的时候碰到过,刚好有几个实用的思路可以分享给你!核心目标是找到离(x,y)最近的、能放下w×h矩形的空闲区域,用C#实现的话,最靠谱的方式是广度优先搜索(BFS),再配合一些优化技巧提升效率,下面给你详细拆解:

核心思路:BFS 优先遍历最近区域

因为我们要找“最近”的位置,BFS天生就是按距离层级遍历的——从目标点(x,y)开始,先检查周围1步内的所有可能位置,再扩展到2步、3步……第一个符合条件的位置就是我们要的,比暴力遍历所有位置再排序高效得多。

具体实现步骤

1. 定义“距离”规则

通常用曼哈顿距离(|x1-x2| + |y1-y2|),它符合网格中“移动步数”的直观感受,计算也快。如果需要更精确的“空间距离”,也可以用欧几里得距离,但曼哈顿在网格场景下更实用。

如果你的需求是“矩形任意点到(x,y)的最小距离最近”,而不是矩形左上角的距离,那可以用我后面提到的优先队列方案,计算矩形区域到目标点的最小距离。

2. 合法位置范围校验

首先明确:矩形的左上角(px, py)必须满足:

  • px + w ≤ 网格宽度
  • py + h ≤ 网格高度
    否则矩形会超出网格边界,直接跳过这类候选点。

3. 空闲区域检查

对每个候选的(px, py),需要验证矩形覆盖的所有单元格都是false(空闲)。这里有两种方式:

方式一:暴力遍历(适合小矩形)

直接遍历矩形范围内的每个单元格,检查是否有true(已占用):

private bool IsRectFree(bool[,] grid, int startX, int startY, int width, int height)
{
    for (int y = startY; y < startY + height; y++)
    {
        for (int x = startX; x < startX + width; x++)
        {
            if (grid[x, y]) // 碰到已占用区域
                return false;
        }
    }
    return true;
}

方式二:前缀和优化(适合大矩形/频繁检查)

提前预处理一个前缀和数组,能在O(1)时间内判断任意矩形区域是否有占用:

// 预处理前缀和:sum[i][j] 表示从(0,0)到(i-1,j-1)的已占用单元格数量
private int[,] ComputePrefixSum(bool[,] grid)
{
    int gridWidth = grid.GetLength(0);
    int gridHeight = grid.GetLength(1);
    int[,] sum = new int[gridHeight + 1, gridWidth + 1];

    for (int y = 0; y < gridHeight; y++)
    {
        int rowTotal = 0;
        for (int x = 0; x < gridWidth; x++)
        {
            rowTotal += grid[x, y] ? 1 : 0;
            sum[y + 1, x + 1] = sum[y, x + 1] + rowTotal;
        }
    }
    return sum;
}

// 快速检查矩形是否空闲:区域内已占用数量为0则合法
private bool IsRectFreeFast(int[,] prefixSum, int startX, int startY, int width, int height)
{
    int endX = startX + width;
    int endY = startY + height;
    int occupiedCount = prefixSum[endY, endX] - prefixSum[startY, endX] - prefixSum[endY, startX] + prefixSum[startY, startX];
    return occupiedCount == 0;
}

4. BFS 遍历实现

用队列存储候选的左上角位置,按距离层级扩展,同时用visited数组避免重复检查:

public (int x, int y)? FindClosestFreeRect(bool[,] grid, int targetX, int targetY, int rectWidth, int rectHeight)
{
    int gridWidth = grid.GetLength(0);
    int gridHeight = grid.GetLength(1);

    // 先判断矩形本身是否能放进网格
    if (rectWidth > gridWidth || rectHeight > gridHeight)
        return null;

    int maxValidPx = gridWidth - rectWidth;
    int maxValidPy = gridHeight - rectHeight;

    Queue<(int x, int y)> queue = new Queue<(int x, int y)>();
    bool[,] visited = new bool[gridWidth, gridHeight];

    // 先把目标点对应的合法左上角加入队列(如果在范围内)
    if (targetX >= 0 && targetX <= maxValidPx && targetY >= 0 && targetY <= maxValidPy)
    {
        queue.Enqueue((targetX, targetY));
        visited[targetX, targetY] = true;
    }

    // 8个方向扩展(确保覆盖所有曼哈顿距离层级)
    var directions = new (int dx, int dy)[] 
    { 
        (1,0), (-1,0), (0,1), (0,-1),
        (1,1), (1,-1), (-1,1), (-1,-1)
    };

    // 预处理前缀和(如果用快速检查的话)
    var prefixSum = ComputePrefixSum(grid);

    while (queue.Count > 0)
    {
        var current = queue.Dequeue();
        int px = current.x;
        int py = current.y;

        // 检查当前位置是否能放下矩形
        if (IsRectFreeFast(prefixSum, px, py, rectWidth, rectHeight))
        {
            return (px, py);
        }

        // 扩展到相邻的候选点
        foreach (var dir in directions)
        {
            int newPx = px + dir.dx;
            int newPy = py + dir.dy;
            if (newPx >= 0 && newPx <= maxValidPx && newPy >= 0 && newPy <= maxValidPy && !visited[newPx, newPy])
            {
                visited[newPx, newPy] = true;
                queue.Enqueue((newPx, newPy));
            }
        }
    }

    // 没有找到可用位置
    return null;
}
进阶优化:优先队列处理精确距离

如果你的需求是“矩形区域到(x,y)的最小距离最近”(比如目标点在某个矩形内部的话,这个矩形就是最近的),可以用PriorityQueue(C# 6及以上支持),按矩形到目标点的最小距离排序,每次取出距离最小的候选点检查:

// 计算矩形到目标点的最小曼哈顿距离
private int GetMinRectDistance(int targetX, int targetY, int px, int py, int w, int h)
{
    int rectLeft = px;
    int rectRight = px + w - 1;
    int rectTop = py;
    int rectBottom = py + h - 1;

    int dx = 0;
    if (targetX < rectLeft) dx = rectLeft - targetX;
    else if (targetX > rectRight) dx = targetX - rectRight;

    int dy = 0;
    if (targetY < rectTop) dy = rectTop - targetY;
    else if (targetY > rectBottom) dy = targetY - rectBottom;

    return dx + dy;
}

// 用优先队列实现的版本
public (int x, int y)? FindClosestFreeRectByMinDistance(bool[,] grid, int targetX, int targetY, int rectWidth, int rectHeight)
{
    int gridWidth = grid.GetLength(0);
    int gridHeight = grid.GetLength(1);

    if (rectWidth > gridWidth || rectHeight > gridHeight)
        return null;

    int maxValidPx = gridWidth - rectWidth;
    int maxValidPy = gridHeight - rectHeight;

    PriorityQueue<(int x, int y), int> pq = new PriorityQueue<(int x, int y), int>();
    bool[,] visited = new bool[gridWidth, gridHeight];

    // 初始化队列,加入目标点对应的合法位置
    if (targetX >= 0 && targetX <= maxValidPx && targetY >= 0 && targetY <= maxValidPy)
    {
        int distance = GetMinRectDistance(targetX, targetY, targetX, targetY, rectWidth, rectHeight);
        pq.Enqueue((targetX, targetY), distance);
        visited[targetX, targetY] = true;
    }

    var directions = new (int dx, int dy)[] 
    { 
        (1,0), (-1,0), (0,1), (0,-1),
        (1,1), (1,-1), (-1,1), (-1,-1)
    };

    var prefixSum = ComputePrefixSum(grid);

    while (pq.Count > 0)
    {
        var current = pq.Dequeue();
        int px = current.x;
        int py = current.y;

        if (IsRectFreeFast(prefixSum, px, py, rectWidth, rectHeight))
        {
            return (px, py);
        }

        foreach (var dir in directions)
        {
            int newPx = px + dir.dx;
            int newPy = py + dir.dy;
            if (newPx >= 0 && newPx <= maxValidPx && newPy >= 0 && newPy <= maxValidPy && !visited[newPx, newPy])
            {
                visited[newPx, newPy] = true;
                int distance = GetMinRectDistance(targetX, targetY, newPx, newPy, rectWidth, rectHeight);
                pq.Enqueue((newPx, newPy), distance);
            }
        }
    }

    return null;
}
注意事项
  • 坐标系统:确认你的网格是x为列、y为行还是反过来,代码中grid.GetLength(0)是第一维度,要对应调整边界判断。
  • 动态网格:如果网格频繁更新(比如经常放置/移除矩形),BFS的方式比预缓存所有空闲位置更合适,因为缓存会频繁失效。
  • 性能:当网格非常大时,可以考虑限制搜索的最大距离(比如只找10步内的位置),避免无意义的遍历。

内容的提问来源于stack exchange,提问作者ADaurio

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 06:38:14