Packing问题
问题描述:如何把任意数量任意尺寸矩形集无重复的放到一个面积最小的封闭矩形中。
算法思想:(为了便于描述,把要找的封闭矩形记为a,封闭矩形的集合记为as,把矩形集合记为rs,n为rs中矩形的个数,把可以插入矩形的位置记为corners)
1.把所有矩形集中的矩形按高度从大到小排序,此时rs[0]高度最大
2.把a初始化为:height = rs[0].height,width = rs[0].width + rs[1].width + ...... + rs[n - 1].width,corners初始化为:坐标顶点
3.把rs[0]放入a中,并把由于rs[0]的插入产生的corner放入corners集合中,删除已经使用的corner,如下图所示:
4.从rs[1]到rs[n - 1],每次插入时选择可以插入的X值最小的corner,插入成功之后,删除已经使用的corner,并且插入新产生的corner,由于a的初始化条件,可以保证rs中的所有矩形都可以插入a中
5.所有矩形插入完成后,对a进行“瘦身”,让a正好包含所有的矩形,并记录此时a的宽度为width,a的面积为area(此时a是所有符合条件的封闭矩形中的‘最狭长’的,令as[0] = a)
6.依次减少a.width,增加a.height,重复3-5,如果a的面积有所减少,则把a插入到as中,直到a.width = rs中最宽矩形的宽度
7.as集合的最后一个元素就是可以包含rs中所有矩形的面积最小的封闭矩形,算法结束
算法实现:
在算法的实现中需要注意的地方:
1.计算由于矩形的插入产生的新的corner(需要考虑很多特殊情况)
2.判断矩形是否可以插入一个corner
3.数据结构的设计
以下程序是算法最核心的方法,插入矩形,并记录新产生的corner
private boolean putIn(PngRect png){ if(pngs == null){ pngs = new ArrayList<PngRect>(); pngs.add(png); png.setStartP(new Point(0,0)); if(png.getHeight() < mHeight){ Rect owner = new Rect(0,png.getStopP().getY(),mWidth,mHeight); Corner corner = new Corner(new Point(0,png.getHeight()),owner); corners.add(corner); } if(png.getWidth() < mWidth){ Rect owner = new Rect(png.getStopP().getX(),0,mWidth,mHeight); Corner corner = new Corner(new Point(png.getWidth(),0),owner); corners.add(corner); } return true; }else{ Corner corner; int x; int y; Rect owner; for(int i = 0; i < corners.size(); ++i){ corner = corners.get(i); x = corner.getPoint().getX(); y = corner.getPoint().getY(); owner = corner.getRect(); Point startP = corner.getPoint(); int endX = owner.right; int endY = owner.bottom; /* * 可以放进该corner,此处存在覆盖问题,需要判断四条边是否有点在已经被使用,如果是,则返回,试下一个corner * v0-----v1 * | | * | a | * | | * v2-----v3 * 同时为了防止完全重叠,还需要判断额外一个点,例如图中的a点 */ if(x + png.getWidth() <= mWidth && y + png.getHeight() <= mHeight){ Point v0 = startP; Point v1 = new Point(startP.getX() + png.getWidth(),startP.getY()); Point v2 = new Point(startP.getX(),startP.getY() + png.getHeight()); Point v3 = new Point(startP.getX() + png.getWidth(),startP.getY() + png.getHeight()); Point a = new Point(startP.getX() + png.getWidth()/2,startP.getY() + png.getHeight()/2); if(contains(v0,v1,true) || contains(v1,v3,false) || contains(v2,v3,true) || contains(v0,v2,false) || contains(a)){//有可能正好落在边上 continue; } // if(contains(a) ||contains(v0) || contains(v1) || contains(v2) || contains(v3) ){//有可能正好落在边上 // continue; // } if(x + png.getWidth() < endX){ corners.add(i + 1,new Corner(new Point(x + png.getWidth(),y), new Rect(x + png.getWidth(),y,mWidth,mHeight)));//此处owner可能为空 } if(y + png.getHeight() < endY){ corners.add(i + 1, new Corner(new Point(x,y + png.getHeight()), new Rect(x,y + png.getHeight(),mWidth,mHeight))); } corners.remove(i); sortCorners(); png.setStartP(corner.getPoint()); pngs.add(png); return true; } } } return false; }
实体类:
Corner
public class Corner { //Corner的起始点 private Point point; //可以填充图片的区域 private Rectangle rect; public Corner(Point point,Rectangle rect) { this.point = point; this.rect = rect; } public Point getPoint() { return point; } public Rectangle getRect() { return rect; } }
PngRect
public class PngRect { private int mHeight; private int mWidth; private Point mStartP;//最左上角的点 private Point mStopP;//最右下角的点 private BufferedImage mBitmap; public PngRect(int width, int height, BufferedImage bitmap) { this.mWidth = width; this.mHeight = height; this.mBitmap = bitmap; } public int getWidth() { return this.mWidth; } public int getHeight() { return this.mHeight; } public void setStartP(Point p) { mStartP = p; mStopP = new Point(p.x + mWidth, p.y + mHeight); } public Point getStartP() { return mStartP; } public Point getStopP() { return mStopP; } public BufferedImage getBitmap() { return mBitmap; } //判断某个点是否包含在矩形内 public boolean contains(Point p) { if (mStartP == null) { return false; } int x = p.x; int y = p.y; if (x > mStartP.getX() && x < mStopP.getX() && y > mStartP.getY() && y < mStopP.getY()) { return true; } return false; } //把pngs中的图片按高度从大到小排序 public static void sortPngRects(ArrayList<PngRect> pngs) { PngRect maxHeightrect; PngRect tempRect; PngRect currentRect; int k; for (int i = 0; i < pngs.size(); ++i) { maxHeightrect = pngs.get(i); currentRect = pngs.get(i); k = i; for (int j = i + 1; j < pngs.size(); ++j) { tempRect = pngs.get(j); if (tempRect.getHeight() > maxHeightrect.getHeight()) { maxHeightrect = tempRect; k = j; } } if (k != i) { pngs.set(i,maxHeightrect); pngs.set(k,currentRect); } } } }