如何完全用正方形填充固定矩形?

问题描述

enter image description here

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

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

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

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

解决方法

由于 Hiroshi Nagamochi 和 Yuusuke Abe,有一个 fast approximation algorithm。我在 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,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";
}

enter image description here

,

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

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

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

  2. 对于每个 a 计算剩余的矩形网格大小,一旦 a*a 正方形被切掉

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

  3. for 循环之后使用最后发现的有效a

    因此只需将 a*a 方格添加到您的输出中,然后对 a*a 方格被切掉后将保留在原始矩形中的 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+a);
        glVertex2i(x,y+a);
        glEnd();
        }
    };
int rec[4]={20,20,760,560}; // x,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,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]+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);
    }
//---------------------------------------------------------------------------

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

preview

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

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

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

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

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...