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

基于寻路算法的可变尺寸网格单元格组合高效生成方案咨询

问题描述

我正在开展一个项目,需从指定起点出发,在N×M网格中生成所有符合要求的单元格组合,需避开无效单元格(黑色填充单元格)。网格中每个单元格宽高可不同,组合形状整体不得超过设定的MAX_WIDTH与MAX_HEIGHT,移动方向支持上、右、下、左四个方向。当前尝试DFS递归实现,但仅适用于小型网格,希望寻求高效生成所有组合或仅最大组合的方案,考虑采用Dijkstra、A*等寻路算法,也可根据顶点数量等维度为形状打分。

现有实现代码

CombinationCreator类

public class CombinationCreator
{
    /// <summary>
    /// 包含单元格二维数组的网格
    /// </summary>
    private readonly Grid _grid; 
    /// <summary>
    /// 当前单元格组合形状的状态,用于递归函数
    /// </summary>
    private List<Cell> CurrentState { get; set; } = new List<Cell>();

    /// <summary>
    /// 扫描单元格时使用的方向列表
    /// </summary>
    private readonly List<(int row, int column)> Directions = new List<(int row, int column)>()
    {
        (0, 1), // 右
        (1, 0), // 下
        (0, -1), // 左
        (-1, 0) // 上
    };

    /// <summary>
    /// 所有可能的组合列表
    /// </summary>
    public List<List<Cell>> Results { get; set; } = new List<List<Cell>>();

    public CombinationCreator(Grid grid)
    {
        _grid = grid;
    }

    /// <summary>
    /// 获取单元格的i、j索引(作为起始索引)
    /// </summary>
    public List<List<Cell>> GetCombinations(int i, int j)
    {
        double MAX_WIDTH = 4;
        double MAX_HEIGHT = 7;
        List<List<Cell>> result = new List<List<Cell>>;
        return GenerateCombination(i, j, MAX_WIDTH, MAX_HEIGHT);
    }

    /// <summary>
    /// 通过扫描网格生成所有可能单元格组合的递归函数
    /// </summary>
    private void GenerateCombinations(int i, int j, double maxWidth, double maxHeight, int depth = 0, double minX = double.MaxValue,
        double maxX = double.MinValue, double minY = double.MaxValue, double maxY = double.MinValue)
    {
        if (depth > 50)
        { // 防止无限递归
            return;
        }

        if (ShouldSavePreviousState(i, j, maxWidth, maxHeight, minX, maxX, minY, maxY))
        { // 如果当前单元格会超出maxWidth和maxHeight,则保存之前的状态
            Results.Add(new List<Cell>(CurrentState));
        }
        else
        {
            if (i >= 0 && i < _grid.Cells.GetLength(0) &&
                j >= 0 && j < _grid.Cells.GetLength(1))
            {
                Cell currentCell = _grid.Cells[i, j];

                if (!currentCell.Marked && currentCell.Valid && !currentCell.IsUsed)
                {
                    currentCell.Marked = true;
                    CurrentState.Add(currentCell);

                    minX = Math.Min(minX, currentCell.TopLeft.x);
                    maxX = Math.Max(maxX, currentCell.BottomRight.x);
                    minY = Math.Min(minY, currentCell.BottomRight.y);
                    maxY = Math.Max(maxY, currentCell.TopLeft.y);

                    foreach (var direction in Directions)
                    { // 遍历所有方向
                        int newRow = i + direction.row;
                        int newCol = j + direction.column;

                        GenerateCombinations(newRow, newCol, maxWidth, maxHeight, depth + 1, minX, maxX, minY, maxY);
                    }

                    CurrentState.RemoveAt(CurrentState.Count - 1);
                    currentCell.Marked = false;
                }
            }
        }
    }

    /// <summary>
    /// 检查当前单元格是否会超出maxWidth和maxHeight,或者处于边界,若是则保存之前的状态
    /// </summary>
    private bool ShouldSavePreviousState(int i, int j, double maxWidth, double maxHeight, double minX = double.MaxValue,
        double maxX = double.MinValue, double minY = double.MaxValue, double maxY = double.MinValue)
    {
        double currentWidth = maxX - minX;
        double currentHeight = maxY - minY;

        if (i >= 0 && i < _grid.Cells.GetLength(0) &&
            j >= 0 && j < _grid.Cells.GetLength(1))
        {
            Cell currentCell = _grid.Cells[i, j];

            if (!currentCell.Marked && currentCell.Valid && !currentCell.IsUsed)
            {
                double nextMinX = Math.Min(minX, currentCell.TopLeft.x);
                double nextMaxX = Math.Max(maxX, currentCell.BottomRight.x);
                double nextMinY = Math.Min(minY, currentCell.BottomRight.y);
                double nextMaxY = Math.Max(maxY, currentCell.TopLeft.y);

                double nextWidth = nextMaxX - nextMinX;
                double nextHeight = nextMaxY - nextMinY;

                if (nextHeight > maxHeight || nextWidth > maxWidth)
                {
                    return true;
                }
            }

            if (!currentCell.Valid || currentCell.IsUsed)
            {
                if (currentHeight > maxHeight || currentWidth > maxWidth)
                {
                    return false;
                }
                return true;
            }
        }

        return false;
    }
}

Cell类

public class Cell
{
    public bool Marked { get; set; } // 标记单元格是否已在扫描中访问过
    public bool Valid { get; set; } // 标记单元格是否可用(白色单元格)
    public bool IsUsed { get; set; } // 标记单元格是否已被其他形状使用
    public Point TopLeft { get; set; } // 左上角坐标点
    public Point BottomRight { get; set; } // 右下角坐标点
}
优化方案思路

一、仅生成最大组合的方案

1. A*启发式搜索

  • 状态定义:每个状态包含当前选中的单元格集合、组合边界(minX/maxX/minY/maxY)、已选单元格数量(核心优先级指标)。
  • 启发函数:估算当前状态可扩展的最大单元格数,比如统计当前边界内剩余的有效未使用单元格数量,或基于连通性做乐观估计。
  • 优先级队列:优先扩展已选单元格数量最多的状态,一旦找到无法再扩展的状态,即可作为候选最大组合,后续直接剪枝所有已选数量小于该值的分支。

2. 分支限界法

  • 维护当前找到的最大组合的单元格数maxCount,搜索过程中若当前状态已选数量+剩余可添加最大数量 < maxCount,直接剪枝该分支。
  • 剩余可添加最大数量可提前预处理:比如计算每个单元格所在连通区域的有效单元格总数,或基于当前边界统计剩余有效单元格。

二、生成所有组合的优化方案

1. 记忆化与状态去重

  • 用哈希表记录已处理过的状态(比如用单元格坐标集合的哈希值作为键),避免重复处理相同组合;若无需区分旋转/翻转的相同组合,可进一步简化状态表示。
  • 优化状态存储:不用保存完整单元格列表,改用起始点+已选单元格相对坐标集合,或二进制掩码(网格规模适中时)。

2. 迭代式DFS替代递归

  • 递归深度限制会丢失部分组合,改用栈模拟递归过程的迭代式DFS,避免栈溢出,同时更灵活控制遍历逻辑。

3. 强化剪枝策略

  • 提前计算单元格加入后的尺寸变化,在进入搜索前过滤掉会超出MAX_WIDTH/MAX_HEIGHT的方向。
  • 对搜索方向排序:优先选择组合尺寸增长较慢的方向,或优先填充密集区域,减少无效分支。

三、通用优化点

  • 预处理网格:提前计算每个有效单元格的连通区域,仅在连通区域内搜索,避免跨无效单元格的无效遍历。
  • 尺寸计算优化:在状态中维护当前组合的边界值,添加单元格时直接更新,减少重复计算。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 14:54:54