问题描述
这是背包算法还是装箱?我找不到确切的解决方案,但基本上我有一个固定的矩形区域,我想用完美的正方形填充它,这些正方形代表我的物品,其中每个都有不同的重量,这会影响它们相对于其他物品的大小。
它们将从左上角到右下角从大到小排序。
此外,即使我需要完美的正方形,最终允许一些非均匀缩放填充整个空间,只要它们仍然保留其相对区域,并且非均匀缩放以尽可能少的数量完成。
我可以使用什么算法来实现这一目标?
解决方法
由于 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";
}
,
好的,让我们假设整数位置和大小(无浮点运算)。要将矩形均匀地划分为规则的正方形网格(尽可能大的正方形),单元格的大小将是矩形大小的最大公约数 GCD。
但是你想要的方块比这少得多,所以我会尝试这样的事情:
-
尝试所有方形尺寸
a
从 1 到较小尺寸的矩形 -
对于每个
a
计算剩余的矩形网格大小,一旦a*a
正方形被切掉所以它只是在
a*a
正方形被切割后将创建的 2 个矩形上再次进行 GCD。如果 2 个矩形的所有 3 个尺寸a
和 GCD 的最小值大于 1(忽略零面积矩形),则将a
视为有效解决方案,因此请记住它。 -
在
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);
}
//---------------------------------------------------------------------------
并预览实际硬编码的矩形:
窗口标题中的数字 8 是生成的方块数。
请注意,这只是针对此问题的非常简单的启动示例。我没有对它进行广泛的测试,所以一旦涉及素数大小或只是不幸的纵横比,那么这可能会导致非常多的正方形......例如,如果矩形大小的 GCD 是 1(素数)......在在这种情况下,您应该调整初始矩形大小(+/-1 或其他)
代码中最重要的只是 compute()
函数和保存输出方块 s[n]
的全局变量...当心 compute()
不会清除列表(为了允许递归),因此您需要在其非递归调用之前设置 n=0
。
为了保持简单,我避免为计算本身使用任何库或动态分配或容器...