【问题标题】:How to fill a fixed rectangle with square pieces entirely?如何用正方形完全填充固定矩形?
【发布时间】:2021-06-24 02:36:11
【问题描述】:

这是背包算法还是装箱?我找不到确切的解决方案,但基本上我有一个固定的矩形区域,我想用完美的正方形填充代表我的物品,其中每个都有不同的重量,这会影响它们相对于其他物品的大小。

它们将从左上角到右下角从大到小排序。

此外,即使我需要完美的正方形,最后也允许一些非均匀缩放填充整个空间,只要它们仍然保留其相对区域,并且非均匀缩放以尽可能少的量完成。

我可以使用什么算法来实现这一点?

【问题讨论】:

  • 我不认为在二进制计算机上总是可能出现完美的正方形......更像是一些浮点精度限制,如矩形大小的 2^-24。我会计算矩形大小的最大公约数(但浮点数)并将其用作正方形的边大小,以均匀填充您的矩形。在第一次迭代中,我们可以将矩形大小的浮点数对齐到相同的指数,并将尾数用作 GCD 的整数。换句话说,将矩形放大,使其大小是精度限制内的整数
  • 一旦你得到正方形网格,如果你愿意,你可以合并其中的一些以形成更大的正方形......看到这个Converting floating ratios to int,你不需要为任何浮点大小的矩形计算 GCD
  • 这似乎是不可能的,尤其是在排序标准方面。我知道packing algorithms 几乎不能控制正方形大小或纵横比。 Treemapping 更简单,但也许你有理由想要正方形。
  • 谢谢大家,什么是 GCD?我刚刚发现它叫做树形映射。我想我可以用那个。我只是觉得完美的正方形看起来更好。同样在我的情况下,我没有那么多物品,比如最多 100 个。
  • 如果您愿意放弃排序,可能会针对树进行优化,从而生成接近 1:1 纵横比的树状图。

标签: algorithm math knapsack-problem packing bin-packing


【解决方案1】:

好的,让我们假设整数位置和大小(没有浮点操作)。要将矩形均匀地划分为规则的正方形网格(尽可能大的正方形),单元格的大小将是矩形大小的最大公约数 GCD

但是你想要比这少得多的正方形,所以我会尝试这样的事情:

  1. 尝试所有正方形尺寸 a 从 1 到更小的矩形尺寸

  2. 对于每个a,一旦切掉a*a 正方形,计算矩形其余部分的朴素正方形网格大小

    所以它只是在 2 个矩形上再次简单地 GCD,一旦 a*a 正方形被切割,就会创建。如果所有 3 种尺寸 a 和 2 个矩形的 GCD 的最小值大于 1(忽略零面积矩形),则将 a 视为有效解决方案,因此请记住它。

  3. for 循环之后使用 last found valida

    所以只需将a*a square 添加到您的输出中,然后对在a*a square 被切断后将保留在原始矩形中的2个矩形再次递归地执行此操作。

这里是简单的 C++/VCL/OpenGL 示例:

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "gl_simple.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
class square                // simple square
    {
public:
    int x,y,a;              // corner 2D position and size
    square(){ x=y=a=0.0; }
    square(int _x,int _y,int _a){ x=_x; y=_y; a=_a; }
    ~square(){}
    void draw()
        {
        glBegin(GL_LINE_LOOP);
        glVertex2i(x  ,y);
        glVertex2i(x+a,y);
        glVertex2i(x+a,y+a);
        glVertex2i(x  ,y+a);
        glEnd();
        }
    };
int rec[4]={20,20,760,560}; // x,y,a,b
const int N=1024;           // max square number
int n=0;                    // number of squares
square s[N];                // squares
//---------------------------------------------------------------------------
int gcd(int a,int b)        // slow euclid GCD
    {
    if(!b) return a;
    return gcd(b, a % b);
    }
//---------------------------------------------------------------------------
void compute(int x0,int y0,int xs,int ys)
    {
    if ((xs==0)||(ys==0)) return;
    const int x1=x0+xs;
    const int y1=y0+ys;
    int a,b,i,x,y;
    square t;
    // try to find biggest square first
    for (a=1,b=0;(a<=xs)&&(a<=ys);a++)
        {
        // sizes for the rest of the rectangle once a*a square is cut of
        if (xs==a) x=0; else x=gcd(a,xs-a);
        if (ys==a) y=0; else y=gcd(a,ys-a);
        // min of all sizes
                          i=a;
        if ((x>0)&&(i>x)) i=x;
        if ((y>0)&&(i>y)) i=y;
        // if divisible better than by 1 remember it as better solution
        if (i>1) b=a;
        } a=b;
    // bigger square not found so use naive square grid division
    if (a<=1)
        {
        t.a=gcd(xs,ys);
        for (t.y=y0;t.y<y1;t.y+=t.a)
         for (t.x=x0;t.x<x1;t.x+=t.a)
          if (n<N){ s[n]=t; n++; }
        }
    // bigest square found so add it to result and recursively process the rest
    else{
        t=square(x0,y0,a);
        if (n<N){ s[n]=t; n++; }
        compute(x0+a,y0,xs-a,a);
        compute(x0,y0+a,xs,ys-a);
        }
    }
//---------------------------------------------------------------------------
void gl_draw()
    {
    glClear(GL_COLOR_BUFFER_BIT);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_TEXTURE_2D);

    // set view to 2D [pixel] units
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(-1.0,-1.0,0.0);
    glScalef(2.0/float(xs),2.0/float(ys),1.0);

    // render input rectangle
    glColor3f(0.2,0.2,0.2);
    glBegin(GL_QUADS);
    glVertex2i(rec[0]       ,rec[1]);
    glVertex2i(rec[0]+rec[2],rec[1]);
    glVertex2i(rec[0]+rec[2],rec[1]+rec[3]);
    glVertex2i(rec[0]       ,rec[1]+rec[3]);
    glEnd();

    // render output squares
    glColor3f(0.2,0.5,0.9);
    for (int i=0;i<n;i++) s[i].draw();

    glFinish();
    SwapBuffers(hdc);
    }
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
    {
    // Init of program
    gl_init(Handle);    // init OpenGL
    n=0; compute(rec[0],rec[1],rec[2],rec[3]);
    Caption=n;
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
    // Exit of program
    gl_exit();
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
    {
    // repaint
    gl_draw();
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
    {
    // resize
    gl_resize(ClientWidth,ClientHeight);
    }
//---------------------------------------------------------------------------

并预览实际硬编码的矩形:

窗口 Caption 中的数字 8 是生成的方块数。

请注意,这只是解决此问题的非常简单的启动示例。我没有对它进行广泛的测试,所以一旦涉及素数大小或只是不幸的纵横比,这可能会导致非常多的正方形......例如,如果矩形大小的 GCD 为 1(素数)......在在这种情况下,您应该调整初始矩形大小(+/-1 或其他)

代码中重要的只是compute() 函数和保存输出方块的全局变量s[n]...注意compute() 不会清除列表(为了允许递归)所以你需要在其非递归调用之前设置n=0

为了简单起见,我避免为计算本身使用任何库或动态分配或容器...

【讨论】:

  • 非常感谢,我将尝试将其移植到 C#。但这是否允许我传递代表每个正方形权重的项目列表? (规范化与否)例如,这样我就可以拥有权重为 1、5、10、20 的框,并且它们将相对于它们的权重正确表示。
  • @JoanVenge 你说的重量是什么意思?如果只是应该显示的值,那么是的,如果是,如果区域那么否,因为这是你之前没有提到的巨大限制,所以算法不包括这些东西......这就是为什么我要求输入......和你的模糊的反应会导致这样的问题......
  • 抱歉,我在 OP 中这样写:“代表我的物品,每个物品的重量都不同,这会影响它们相对于其他物品的尺寸”基本上所有这些重量值,它们的总和将是总面积将承载广场的整个空间。而且每个方块都会使用与其权重成比例的面积,因此权重值为 2 的面积将是权重值 1 的两倍。这很难添加吗?
  • @JoanVenge 分享具体示例输入!
  • 样本输入是我持有的硬币,所以我持有的美元价值越多,它们在树形图中占据的区域就越大,类似于我在 cmets 中发布的关于加密货币市场的图像面积代表市值的上限树形图。
【解决方案2】:

有一个 fast approximation algorithm 来自 Hiroshi Nagamochi 和 Yuusuke Abe。我在 C++ 中实现了它,注意获得最坏情况的 O(n log n) 时间实现,最坏情况的递归深度为 O(log n)。如果 n ≤ 100,这些预防措施可能是不必要的。

#include <algorithm>
#include <iostream>
#include <random>
#include <vector>

namespace {

struct Rectangle {
  double x;
  double y;
  double width;
  double height;
};

Rectangle Slice(Rectangle &r, const double beta) {
  const double alpha = 1 - beta;
  if (r.width > r.height) {
    const double alpha_width = alpha * r.width;
    const double beta_width = beta * r.width;
    r.width = alpha_width;
    return {r.x + alpha_width, r.y, beta_width, r.height};
  }
  const double alpha_height = alpha * r.height;
  const double beta_height = beta * r.height;
  r.height = alpha_height;
  return {r.x, r.y + alpha_height, r.width, beta_height};
}

void LayoutRecursive(const std::vector<double> &reals, const std::size_t begin,
                     std::size_t end, double sum, Rectangle rectangle,
                     std::vector<Rectangle> &dissection) {
  while (end - begin > 1) {
    double suffix_sum = reals[end - 1];
    std::size_t mid = end - 1;
    while (mid > begin + 1 && suffix_sum + reals[mid - 1] <= 2 * sum / 3) {
      suffix_sum += reals[mid - 1];
      mid -= 1;
    }
    LayoutRecursive(reals, mid, end, suffix_sum,
                    Slice(rectangle, suffix_sum / sum), dissection);
    end = mid;
    sum -= suffix_sum;
  }
  dissection.push_back(rectangle);
}

std::vector<Rectangle> Layout(std::vector<double> reals,
                              const Rectangle rectangle) {
  std::sort(reals.begin(), reals.end());
  std::vector<Rectangle> dissection;
  dissection.reserve(reals.size());
  LayoutRecursive(reals, 0, reals.size(),
                  std::accumulate(reals.begin(), reals.end(), double{0}),
                  rectangle, dissection);
  return dissection;
}

std::vector<double> RandomReals(const std::size_t n) {
  std::vector<double> reals(n);
  std::exponential_distribution<> dist;
  std::default_random_engine gen;
  for (double &real : reals) {
    real = dist(gen);
  }
  return reals;
}

} // namespace

int main() {
  const std::vector<Rectangle> dissection =
      Layout(RandomReals(100), {72, 72, 6.5 * 72, 9 * 72});
  std::cout << "%!PS\n";
  for (const Rectangle &r : dissection) {
    std::cout << r.x << " " << r.y << " " << r.width << " " << r.height
              << " rectstroke\n";
  }
  std::cout << "showpage\n";
}

【讨论】:

  • 看起来不像正方形
  • @Spektre "允许一些非均匀缩放"
  • 引用 OP:the non-uniform scaling is done with the least possible amount 我在你的输出中没有看到任何正方形 ...
  • @Spektre 这似乎是一个难题。随意发布您自己的答案。
  • 我会,但是有很多关于输入缺失的澄清,没有它这是浪费时间来回答然后意识到 OP 作者想要不同的输入组合......也看起来像 (s)he尽管有很高的代表,但也缺少为此所需的数学技能(从关于 GCD 是什么的问题中假设)......但这可能是语言太不同了......我经常不理解英语中的一些数学,因为它使用不同的符号
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-01
  • 1970-01-01
  • 2017-04-27
  • 2022-10-06
相关资源
最近更新 更多