我正在研究特定的布局算法,以便在基于单位的网格中显示照片。所需的行为是让每张照片逐行放在下一个可用空间中。
由于很容易有一千张照片的位置需要立即计算,效率非常重要。
这个问题是否已经用现有算法解决了? 如果没有,我怎样才能使它尽可能高效?
编辑 关于定位: 我现在基本上做的是逐个单元格迭代网格的每一行,直到找到适合该元素的空间。这就是为什么将4放在2旁边的原因。
答案 0 :(得分:2)
如何按宽度保留下一个可用行的列表?最初,下一个可用行列表如下所示:
(0,0,0,0,0)
当您添加第一张照片时,它看起来像
(0,0,0,0,1)
然后
(0,0,0,2,2)
然后
(0,0,0,3,3)
然后
(1,1,1,4,4)
最后的照片不会改变列表。
这可能是有效的,因为你只维护一个小列表,在每次迭代时更新一点(而不是每次搜索整个空间。它有点复杂 - 可能有一种情况(高照片)标称的下一个可用行不起作用,然后您可以默认使用现有方法。但总的来说,我认为这应该节省相当多的时间,但代价是增加一点复杂性。
<强>更新强> 响应@ matteok对coordinateForPhoto(width,height)方法的请求:
假设我将该数组称为“nextAvailableRowByWidth”。
public Coordinate coordinateForPhoto(width, height) {
int rowIndex = nextAvailableRowByWidth[width + 1]; // because arrays are zero-indexed
int[] row = space[rowIndex]
int column = findConsecutiveEmptySpace(width, row);
for (int i = 1; i < height; i++) {
if (!consecutiveEmptySpaceExists(width, space[i], column)) {
return null;
// return and fall back on the slow method, starting at rowIndex
}
}
// now either you broke out and are solving some other way,
// or your starting point is rowIndex, column. Done.
return new Coordinate(rowIndex, column);
}
更新#2 响应@ matteok关于如何更新nextAvailableRowByWidth数组的请求:
好的,所以你刚刚在行R放置了一张高度为H,宽度为W的新照片。数组中任何小于R的元素都没有改变(因为这个改变不影响他们的行,所以如果在放置照片之前该行中有3个连续可用空间,则之后仍有3个连续可用空间。需要检查范围(R,R + H)中的每个元素,因为它可能已受到影响。让我们假设一个方法maxConsecutiveBlocksInRow() - 因为这很容易写,对吧?
public void updateAvailableAfterPlacing(int W, int H, int R) {
for (int i = 0; i < nextAvailableRowByWidth.length; i++) {
if (nextAvailableRowByWidth[i] < R) {
continue;
}
int r = R;
while (maxConsecutiveBlocksInRow(r) < i + 1) {
r++;
}
nextAvailableRowByWidth[i] = r;
}
}
我认为应该这样做。
答案 1 :(得分:1)
矩阵怎么样(你的例子是5x9),其中每个单元格都有一个表示左上角距离的值(例如(行+ 1)*(列+ 1)[仅当你的+1时才需要+1]第一行和值为0])。在此矩阵中,您将查找具有最低值的区域(当汇总空单元格的值时)。 第二矩阵(或第一矩阵的第三维)存储每个单元的状态。
编辑:
int[][] grid = new int[9][5];
int[] filledRows = new int [9];
int photowidth = 2;
int photoheight = 1;
int emptyRowCounter = 0;
boolean photoFits = true;
for(int i = 0; i < grid.length; i++){
for(int m = 0; m < filledRows.length; m++){
if(filledRows[m]-(photoHeight-1) > i || filledRows[m]+(photoHeight-1) < i){
for(int j = 0; j < grid[i].length; j++){
if(grid[i][j] == 0){
for(int k = 0; k < photowidth; k++){
for(int l = 0; k < photoheight){
if(grid[i+l][j+k]!=0){
photoFits = false;
}
}
}
} else{
emptyRowCounter++;
}
}
if(photoFits){
//place Photo at i,j
}
if(emptyRowCounter == 5){
filledRows[i] = 1;
}
}
}
}
答案 2 :(得分:0)
在你上面的gif中,很好地说有一张照片(5)可以放入(1)和(2)左边的间隙。我的直觉表明我们希望避免创造这样的差距。这是一个应该避免这些差距的想法。
维护一个“开放区域”列表,其中一个开放区域有一个int leftBoundary,一个int topBoundary和一个可选的int bottomBoundary。第一个开放区域就是整个网格(leftBoundary:0,topBoundary:0,bottom:null)。
按照高度对照片进行排序,按宽度打破关系。
在您放置所有照片之前:
选择最高的照片(如果是领带,请选择最宽的最高照片)。找到它可以容纳的第一个开放区域(例如grid.Width - region.leftBoundary&gt; = photo.Width)。将照片放在该区域的左上角。放置此照片时,它可能会跨越该区域的整个宽度或高度。
如果它跨越区域的宽度和高度,则该区域被填充!从开放区域列表中删除此区域。
如果它跨越宽度而不是高度,请将照片的高度添加到该区域的topBoundary。
如果它跨越高度而不是宽度,请将照片的宽度添加到该区域的leftBoundary。
如果它没有跨越边界的高度或宽度,我们将在概念上将此区域划分为两个:一个区域将直接覆盖此照片右侧的空间(称之为rightRegion),并且另一个区域将覆盖该区域下方的空间(称之为区域)。
rightRegion = { leftBoundary = parentRegion.leftBoundary + photo.width, topBoundary = parentRegion.topBoundary, bottomBoundary = parentRegion.topBoundary + photo.height }
belowRegion = { leftBoundary = 0, topBoundary = parentRegion.topBoundary + photo.height, bottomBoundary = parentRegion.bottomBoundary }
用rightRegion替换开放区域列表中的当前区域,并在rightRegion之后直接插入belowRegion。
您可以直观地看出这个算法如何对您的示例起作用:首先,它会对照片进行排序:(2,3,4,1,5)。
它考虑2,它适合第一个区域(整个网格)。当它在左上角放置2时,它将该区域直接分割到2的右边空间和2以下的空间。
然后,它考虑3.它依次考虑开放区域。第一个开放区域位于右侧2. 3适合那里,所以它就在那里。它跨越了该区域的宽度,因此该区域的topBoundary向下调整。
然后,它考虑4.它再次适合第一个开放区域,所以它在那里放置4。 4跨越区域的高度,因此区域的leftBoundary向右调整。
然后,1被放入4右边的1x1间隙,填补其区域。最后,5只低于2。