【问题标题】:Algorithm for maximizing coverage of rectangular area with scaling tiles使用缩放图块最大化矩形区域覆盖的算法
【发布时间】:2011-04-21 01:37:32
【问题描述】:

我有N 需要放置在固定大小的矩形表面(工具箱)内的可缩放方形图块(按钮)。我想以相同的大小呈现所有按钮。

我怎样才能找到最佳的瓷砖尺寸,以提供被瓷砖覆盖的矩形表面的最大面积。

【问题讨论】:

  • 工具箱在水平和垂直方向上都有固定的尺寸吗?你有按钮的最小和/或最大尺寸吗?
  • @ebynum 是的,X 和 Y 都是预定义的。是的,按钮有一个最大尺寸,但如果发现最佳尺寸高于最大值,我可以改用最大值(所以它并没有真正改变 IMO 的问题)。
  • 我只想指出,如果应用于非矩形多边形形状,它具有巨大的商业应用价值。制造行业始终需要“嵌套”形状以实现最大产品密度的产品。
  • @entens 你应该说“方形制造业”。这就像说解决 2SAT 对 SAT 有巨大的应用。不,一般适用于具体,而不是相反。
  • @piccolbo 我更多地考虑切割/烧制钢板切口。你留下的骨架越少,你的利润率就越高。像 SigmaNEST 这样尝试优化切割模式的产品大大提高了大多数钢材切割操作的投资回报率。但该原则适用于任何从板材切割零件的业务。

标签: algorithm language-agnostic user-interface geometry maximize


【解决方案1】:

WH 为矩形的宽度和高度。

s 为正方形的边长。

那么你可以放入矩形的正方形n(s)的数量是floor(W/s)*floor(H/s)。你想找到s的最大值,n(s) >= N

如果您根据s 绘制平方数,您将得到一个分段常数函数。不连续点位于值W/iH/j,其中ij 贯穿正整数。

您想找到最小的i 对应的n(W/i) >= N,以及类似的最小的j 对应的n(H/j) >= N。将这些最小值称为i_minj_min。那么W/i_minH/j_min 中最大的就是你想要的s

s_max = max(W/i_min,H/j_min)

要查找i_minj_min,只需进行蛮力搜索:对于每个,从 1 开始,测试并递增。

如果N非常大,从1开始搜索ij可能会令人反感(尽管很难想象性能会有任何明显的差异) .在这种情况下,我们可以如下估计起始值。首先,瓷砖面积的大致估计是W*H/N,对应于sqrt(W*H/N)的一侧。如果W/i <= sqrt(W*H/N),那么i >= ceil(W*sqrt(N/(W*H))),类似j >= ceil(H*sqrt(N/(W*H)))

因此,我们可以从i = ceil(sqrt(N*W/H))j = ceil(sqrt(N*H/W))) 开始,而不是在i=1j=1 开始循环。并且 OP 建议 roundceil 工作得更好——最坏的情况是额外的迭代。

以下是用 C++ 阐明的算法:

#include <math.h>
#include <algorithm>
// find optimal (largest) tile size for which
// at least N tiles fit in WxH rectangle
double optimal_size (double W, double H, int N)
{
    int i_min, j_min ; // minimum values for which you get at least N tiles 
    for (int i=round(sqrt(N*W/H)) ; ; i++) {
        if (i*floor(H*i/W) >= N) {
            i_min = i ;
            break ;
        }
    }
    for (int j=round(sqrt(N*H/W)) ; ; j++) {
        if (floor(W*j/H)*j >= N) {
            j_min = j ;
            break ;
        }
    }
    return std::max (W/i_min, H/j_min) ;
}

上面是为了清楚起见而写的。代码可以大大收紧如下:

double optimal_size (double W, double H, int N)
{
    int i,j ;
    for (i = round(sqrt(N*W/H)) ; i*floor(H*i/W) < N ; i++){}
    for (j = round(sqrt(N*H/W)) ; floor(W*j/H)*j < N ; j++){}
    return std::max (W/i, H/j) ;
}

【讨论】:

  • @Kendall,这个解决方案有什么理由不适合你吗?
  • @brainjamin,你能为我们运行一个例子吗?如果最优 i 与最优 j 不同,则 s 与 s 不同。但是如果 W >> H 这显然是这种情况。所以我认为你的方法是矛盾的。
  • @piccolbo,我已经编辑了答案的最后一部分,试图澄清算法。我引入了 i_min、j_min、s_max 来区分实际的极值。
  • @brainjam 我希望有一个非暴力解决方案(在某些情况下,N 可能非常大)。我想我想出了一种猜测最佳点在哪里的方法。您的解决方案也很幼稚。除了尝试每一种可能性,然后选择最好的之外,它实际上并没有做任何事情。
  • @Kendall,很公平。我添加了对起始 i 和 j 的估计。在我有限的实验中,我发现估计值要么准确,要么相差 1。
【解决方案2】:

我相信这可以作为一个约束最小化问题来解决,这需要一些基本的微积分。 .

定义:

a, l -> rectangle sides
   k -> number of squares
   s -> side of the squares

你必须最小化函数:

   f[s]:= a * l/s^2 - k

受约束:

  IntegerPart[a/s] IntegerPart[l/s] - k >= 0
  s > 0

我编写了一个小 Mathematica 函数来解决这个问题

  f[a_, l_, k_] :=  NMinimize[{a l/s^2 - k , 
                               IntegerPart[a/s] IntegerPart[l/s] - k >= 0, 
                               s > 0}, 
                               {s}]     

由于公式与上述相同,因此易于阅读。

使用这个函数,我制作了一个分配6个方块的表格

据我所知,结果是正确的。

正如我所说,您可以为您的环境使用标准微积分包,或者您也可以开发自己的最小化算法和程序。如果您决定选择最后一个选项,请按铃,我会提供一些好的建议。

HTH!

编辑

只是为了好玩,我用结果做了一个情节。

对于 31 个图块:

编辑2:特征参数

问题具有三个特征参数:

  1. 瓷砖的最终尺寸
  2. 瓷砖数量
  3. 包围矩形的比率 l/a

也许最后一个结果可能有点令人惊讶,但很容易理解:如果您对要放置的 7x5 矩形和 6 个瓷砖有疑问,请查看上表,正方形的大小将为 2.33。现在,如果您有一个 70x50 的矩形,显然生成的图块将是 23.33,与问题等距缩放。

因此,我们可以利用这三个参数并构建它们关系的 3D 图,并最终将曲线与一些更易于计算的函数匹配(例如使用最小二乘法或计算等值区域)。

无论如何,得到的缩放图是:

【讨论】:

  • 目标函数不应该是a * l - k * s ^ 2吗?来表示最大区域被瓷砖覆盖的原始约束?您的目标函数是平方数,当您认为平方数必须是整数时,这可能会变得很奇怪。
  • @marimon 你可以考虑这两种方式......结果是一样的(已经跑了编)
  • 一想到库恩塔克的情况就让我头疼!
【解决方案3】:

我意识到这是一个旧线程,但我最近以一种我认为有效且总是给出正确答案的方式解决了这个问题。它旨在保持给定的纵横比。如果您希望孩子(在这种情况下为按钮)是方形的,只需使用 1 的纵横比即可。我目前在一些地方使用此算法,效果很好。

        double VerticalScale; // for the vertical scalar: uses the lowbound number of columns
        double HorizontalScale;// horizontal scalar: uses the highbound number of columns
        double numColumns; // the exact number of columns that would maximize area
        double highNumRows; // number of rows calculated using the upper bound columns
        double lowNumRows; // number of rows calculated using the lower bound columns
        double lowBoundColumns; // floor value of the estimated number of columns found
        double highBoundColumns; // ceiling value of the the estimated number of columns found


        Size rectangleSize = new Size(); // rectangle size will be used as a default value that is the exact aspect ratio desired.
        //
        // Aspect Ratio = h / w
        // where h is the height of the child and w is the width
        //
        // the numerator will be the aspect ratio and the denominator will always be one
        // if you want it to be square just use an aspect ratio of 1
        rectangleSize.Width = desiredAspectRatio;
        rectangleSize.Height = 1;

        // estimate of the number of columns useing the formula:
        //                          n * W * h       
        //  columns = SquareRoot(  -------------  )
        //                            H * w          
        //
        // Where n is the number of items, W is the width of the parent, H is the height of the parent,
        // h is the height of the child, and w is the width of the child
        numColumns = Math.Sqrt( (numRectangles * rectangleSize.Height * parentSize.Width) / (parentSize.Height * rectangleSize.Width) );

        lowBoundColumns = Math.Floor(numColumns);
        highBoundColumns = Math.Ceiling(numColumns);

        // The number of rows is determined by finding the floor of the number of children divided by the columns
        lowNumRows = Math.Ceiling(numRectangles / lowBoundColumns);
        highNumRows = Math.Ceiling(numRectangles / highBoundColumns);

        // Vertical Scale is what you multiply the vertical size of the child to find the expected area if you were to find
        // the size of the rectangle by maximizing by rows
        //
        //                      H
        // Vertical Scale = ----------
        //                    R * h
        //
        // Where H is the height of the parent, R is the number of rows, and h is the height of the child
        //
        VerticalScale = parentSize.Height / lowNumRows * rectangleSize.Height;

        //Horizontal Scale is what you multiply the horizintale size of the child to find the expected area if you were to find
        // the size of the rectangle by maximizing by columns
        //
        //                      W
        // Vertical Scale = ----------
        //                    c * w
        //
        //Where W is the width of the parent, c is the number of columns, and w is the width of the child
        HorizontalScale = parentSize.Width / (highBoundColumns * rectangleSize.Width);

        // The Max areas are what is used to determine if we should maximize over rows or columns
        //  The areas are found by multiplying the scale by the appropriate height or width and finding the area after the scale
        //                      
        // Horizontal Area =  Sh * w * ( (Sh * w) / A )
        //                     
        // where Sh is the horizontal scale, w is the width of the child, and A is the aspect ratio of the child
        //
        double MaxHorizontalArea = (HorizontalScale * rectangleSize.Width) * ((HorizontalScale * rectangleSize.Width) / desiredAspectRatio);
        //
        //                       
        // Vertical Area =   Sv * h * (Sv * h) * A
        // Where Sv isthe vertical scale, h is the height of the child, and A is the aspect ratio of the child
        //
        double MaxVerticalArea = (VerticalScale * rectangleSize.Height) * ((VerticalScale * rectangleSize.Height) * desiredAspectRatio);


        if (MaxHorizontalArea >= MaxVerticalArea ) // the horizontal are is greater than the max area then we maximize by columns
        {
            // the width is determined by dividing the parent's width by the estimated number of columns
            // this calculation will work for NEARLY all of the horizontal cases with only a few exceptions
            newSize.Width = parentSize.Width / highBoundColumns; // we use highBoundColumns because that's what is used for the Horizontal
            newSize.Height = newSize.Width / desiredAspectRatio; // A = w/h or h= w/A

            // In the cases that is doesnt work it is because the height of the new items is greater than the 
            // height of the parents. this only happens when transitioning to putting all the objects into
            // only one row
            if (newSize.Height * Math.Ceiling(numRectangles / highBoundColumns) > parentSize.Height)
            {
                //in this case the best solution is usually to maximize by rows instead
                double newHeight = parentSize.Height / highNumRows;
                double newWidth = newHeight * desiredAspectRatio;

                // However this doesn't always work because in one specific case the number of rows is more than actually needed
                // and the width of the objects end up being smaller than the size of the parent because we don't have enough
                // columns
                if (newWidth * numRectangles < parentSize.Width)
                {
                    //When this is the case the best idea is to maximize over columns again but increment the columns by one
                    //This takes care of it for most cases for when this happens.
                    newWidth = parentSize.Width / Math.Ceiling(numColumns++);
                    newHeight = newWidth / desiredAspectRatio;

                    // in order to make sure the rectangles don't go over bounds we
                    // increment the number of columns until it is under bounds again.
                    while (newWidth * numRectangles > parentSize.Width)
                    {
                        newWidth = parentSize.Width / Math.Ceiling(numColumns++);
                        newHeight = newWidth / desiredAspectRatio;
                    }

                    // however after doing this it is possible to have the height too small.
                    // this will only happen if there is one row of objects. so the solution is to make the objects'
                    // height equal to the height of their parent
                    if (newHeight > parentSize.Height)
                    {
                        newHeight = parentSize.Height;
                        newWidth = newHeight * desiredAspectRatio;
                    }
                }

                // if we have a lot of added items occaisionally the previous checks will come very close to maximizing both columns and rows
                // what happens in this case is that neither end up maximized

                // because we don't know what set of rows and columns were used to get us to where we are
                // we must recalculate them with the current measurements
                double currentCols = Math.Floor(parentSize.Width / newWidth); 
                double currentRows = Math.Ceiling(numRectangles/currentCols);
                // now we check and see if neither the rows or columns are maximized
                if ( (newWidth * currentCols ) < parentSize.Width && ( newHeight * Math.Ceiling(numRectangles/currentCols) ) < parentSize.Height)
                {
                    // maximize by columns first
                    newWidth = parentSize.Width / currentCols;
                    newHeight = newSize.Width / desiredAspectRatio;

                    // if the columns are over their bounds, then maximized by the columns instead
                    if (newHeight * Math.Ceiling(numRectangles / currentCols) > parentSize.Height)
                    {
                        newHeight = parentSize.Height / currentRows;
                        newWidth = newHeight * desiredAspectRatio;
                    }
                }

                // finally we have the height of the objects as maximized using columns
                newSize.Height = newHeight;
                newSize.Width = newWidth;

            }

        }
        else
        {
            //Here we use the vertical scale. We determine the height of the objects based upong
            // the estimated number of rows.
            // This work for all known cases
            newSize.Height = parentSize.Height / lowNumRows; 
            newSize.Width = newSize.Height * desiredAspectRatio;
        }

在算法结束时,'newSize' 保持适当的大小。这是用 C# 编写的,但很容易移植到其他语言。

【讨论】:

【解决方案4】:

第一个非常粗略的启发式是采取

s = floor( sqrt( (X x Y) / N) )

其中s 是按钮边长,XY 是工具箱的宽度和高度,N 是按钮的数量。

在这种情况下,s 将是最大可能的边长。但是,不一定可以将这组按钮映射到工具栏上。

想象一个工具栏,它是 20 x 1 单位,带有 5 个按钮。启发式将给您一个边长为 2(面积为 4),总覆盖面积为 20。但是,每个按钮的一半将在工具栏之外。

【讨论】:

    【解决方案5】:

    我会在这里采用迭代方法。 我会检查是否可以将所有按钮放在一行中。 如果没有,请检查是否可以放入两行,依此类推。

    假设 W 是工具箱中较小的一侧。 H是另一边。

    对于每次迭代,我都会按顺序检查可能的最佳和最坏情况。最好的情况意味着,假设它是第 n 次迭代,将尝试 W/n X W/n 大小的按钮。如果 h 值足够,那么我们就完成了。如果不是,最坏的情况是尝试 (W/(n+1))+1 X (W/(n+1))+1 大小的按钮。如果可以安装所有按钮,那么我会尝试 W/n 和 (W/(n+1))+1 之间的二等分法。如果不是,则在 n+1 处继续迭代。

    【讨论】:

      【解决方案6】:

      设 n(s) 是可以适合和 s 边的正方形的数量。令 W, H 为要填充的矩形的边。那么 n(s) = floor(W/s)* floor(H/s)。这是 s 中的单调递减函数,也是分段常数,因此您可以对二进制搜索进行轻微修改以找到最小的 s,使得 n(s) >= N 但 n(s+eps) = N 那么 l = min(W/floor(W/t), H/floor(H/t)) 否则 u = max(W/floor(W/t), H/floor(H/t))。当 u 和 l 在连续迭代中保持不变时停止。 所以它就像二分搜索,但你利用了函数是分段常数的事实,并且当 W 或 H 是 s 的精确倍数时变化点。不错的小问题,谢谢推荐。

      【讨论】:

        【解决方案7】:

        我们知道任何最优解(可能有两个)都会水平或垂直填充矩形。如果您找到了未填充一维矩形的最佳解决方案,您可以随时增加图块的比例以填充一维。

        现在,任何最大化覆盖表面的解决方案都将具有接近矩形纵横比的纵横比。解的纵横比为vertical tile count/horizontal tile count(矩形纵横比为Y/X)。

        你可以通过强制Y&gt;=X来简化问题;换句话说,如果X&gt;Y,则转置矩形。这允许您只考虑纵横比 >= 1,只要您记得将解转回即可。

        计算出纵横比后,您想找到V/H ~= Y/X 问题的解决方案,其中V 是垂直平铺计数,H 是水平平铺计数。您将找到最多三个解决方案:最接近Y/XV/HV+1V-1。此时,只需使用VH 根据规模计算覆盖率并取最大值(可能不止一个)。

        【讨论】:

        • 您最初的陈述不正确。如果我有 4 个瓷砖和一个 5x5 的表面,我的最佳正方形大小将是 2x2,总共覆盖 5x5 中的 4x4,对吗? 3x3 方格(每个 9 个)将覆盖总共 36 个单位,而表面总共只有 25 个单位。
        • @ebynum,我假设比例不是整数。如果是,那么我不知道我的解决方案是否是最优的。如果他们不是,那么我的就是。
        • 据我了解,瓷砖不是整数,因为它们是工具箱上的按钮。好吧,可能它们毕竟是整数(像素),但由于像素大小可能被认为比工具箱大小小得多,所以问题是连续的(即在 R 中)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-11-06
        • 2011-05-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多