简体   繁体   English

如何在高度图中找到体积最大的长方体? (低复杂度)

[英]How can you find the cuboid with the greatest volume in a heightmap? (with low complexity)

I need to find the cuboid with the greatest volume, contained within a 2D-heightmap.我需要找到包含在 2D 高度图中的体积最大的长方体。 The heightmap is an array of size w*d where w is width, h is height and d is depth.高度图是一个大小为w*d的数组,其中w是宽度, h是高度, d是深度。 In C, this would look along the lines of:在 C 中,这看起来像:

unsigned heightmap[w][d]; // all values are <= h

I already know that there is a naive algorithm which can solve this with O(w*d*h) complexity.我已经知道有一个简单的算法可以用O(w*d*h)复杂度解决这个问题。 However, I suspect that there is a more optimal method out there.但是,我怀疑那里有更优化的方法。 It works as follows, in pythonic pseudocode:它的工作原理如下,在pythonic伪代码中:

resultRectangle = None
resultHeight = None
resultVolume = -1

# iterate over all heights
for loopHeight in range(0, h):
    # create a 2D bitmap from our heightmap where a 1 represents a height >= loopHeight
    bool bitmap[w][d]
    for x in range(0, w):
        for y in range(0, d):
            bitmap[x][y] = heightmap[x][y] >= loopHeight

    # obtain the greatest-volume cuboid at this particular height
    maxRectangle = maxRectangleInBitmap(bitmap)
    volume = maxRectangle.area() * loopHeight

    # compare it to our current maximum and replace it if we found a greater cuboid
    if volume > resultVolume:
        resultHeight = loopHeight
        resultVolume = volume
        resultRectangle = maxRectangle

resultCuboid = resultRectangle.withHeight(resultHeight)

Finding the greatest area of all 1 in a rectangle is a known problem with O(1) complexity per pixel or O(w*d) in our case.在矩形中找到所有1中最大的区域是一个已知的问题,每个像素的复杂度为O(1)或在我们的例子中为O(w*d) The total complexity of the naive approach is thus O(w*h*d) .因此,朴素方法的总复杂度为O(w*h*d)

So as I already stated, I was wondering if we can beat this complexity.正如我已经说过的,我想知道我们是否可以克服这种复杂性。 Perhaps we can get it down to O(w*d * log(h)) by searching through heights more intelligently instead of "brute-forcing" all of them.也许我们可以通过更智能地搜索高度而不是“蛮力”所有高度来将其简化为O(w*d * log(h))

The answer to this question Find largest cuboid containing only 1's in an NxNxN binary array by Evgeny Kluev seems to take a similar approach, but it falsely(?) assumes that the volumes which we would find at these heights form a unimodal function. Evgeny Kluev 对在 NxNxN 二进制数组中查找仅包含 1 的最大长方体这个问题的答案似乎采用了类似的方法,但它错误地(?)假设我们会在这些高度找到的体积形成单峰函数。 If this was the case, we could use Golden Section Search to choose heights more intelligently, but I don't think we can.如果是这样,我们可以使用黄金分割搜索更智能地选择高度,但我认为我们不能。

Here is an idea, with a significant assumption.这是一个想法,有一个重要的假设。 pseudo-code:伪代码:

P <- points from heightmap sorted by increasing height.
R <- set of rectangles. All maximal empty sub-rectangles for the current height.
R.add(Rectangle(0,0,W,H)
result = last_point_in(P).height()
foreach(p in P):
   RR <- rectangles from R that overlap P (can be found in O(size(RR)), possibly with some logarithmic factors)
   R = R - RR
   foreach(r in RR)
       result = max(result, r.area() * p.height())
       split up r, adding O(1) new rectangles to R.
return result

The assumption, which I have a gut feeling about, but can't prove, is that RR will be O(1) size on average.我有直觉但无法证明的假设是 RR 的平均大小为 O(1)。

Edit: to clarify the "splittting", if we split at point p:编辑:澄清“分裂”,如果我们在点 p 分裂:

AAAAADFFF
AAAAADFFF
AAAAADFFF
BBBBBpGGG
CCCCCEHHH
CCCCCEHHH

We generate new rectangles consisting of: ABC, CEH, FGH, ADF, and add them to R.我们生成新的矩形,包括:ABC、CEH、FGH、ADF,并将它们添加到 R。

OK, another take.好的,再来一张。 Most "meat" is in the go function.大多数“肉”都在go函数中。 It uses the same "splitting" concept as in my other answer, but uses top-down dynamic programming with memoization.它使用与我的其他答案相同的“拆分”概念,但使用带有记忆化的自顶向下动态编程。 rmq2d implements 2D Range Minimum Query. rmq2d实现二维范围最小查询。 for size 1000x1000 it takes about 30 seconds (while using 3GB of memory).对于 1000x1000 大小,大约需要 30 秒(使用 3GB 内存时)。

#include <iostream>
#include <vector>
#include <cassert>
#include <set>
#include <tuple>
#include <memory.h>
#include <limits.h>

using namespace std;

constexpr int ilog2(int x){
    return 31 - __builtin_clz(x);
}

const int MAX_DIM = 100;




template<class T>
struct rmq2d{
    struct point{
        int x,y;

        point():x(0),y(0){}
        point(int x,int y):x(x),y(y){}
    };
    typedef point array_t[MAX_DIM][ilog2(MAX_DIM)+1][MAX_DIM];

    int h, logh;
    int w, logw;
    vector<vector<T>> v;

    array_t *A;

    rmq2d(){A=nullptr;}

    rmq2d &operator=(const rmq2d &other){
        assert(sizeof(point)==8);
        if(this == &other) return *this;
        if(!A){
            A = new array_t[ilog2(MAX_DIM)+1];
        }
        v=other.v;
        h=other.h;
        logh = other.logh;
        w=other.w;
        logw=other.logw;
        memcpy(A, other.A, (ilog2(MAX_DIM)+1)*sizeof(array_t));
        return *this;
    }

    rmq2d(const rmq2d &other){
        A = nullptr;
        *this = other;
    }

    ~rmq2d(){
        delete[] A;
    }


    T query(point pos){
        return v[pos.y][pos.x];
    }

    rmq2d(vector<vector<T>> &v) : v(v){
        A = new array_t[ilog2(MAX_DIM)+1];
        h = (int)v.size();
        logh = ilog2(h) + 1;
        w = (int)v[0].size();
        logw = ilog2(w) + 1;

        for(int y=0; y<h; ++y){
            for(int x=0;x<w;x++) A[0][y][0][x] = {x, y};

            for(int jx=1; jx<logw; jx++){
                int sz = 1<<(jx-1);
                for(int x=0; x+sz < w; x++){
                    point i1 = A[0][y][jx-1][x];
                    point i2 = A[0][y][jx-1][x+sz];
                    if(query(i1) < query(i2)){
                        A[0][y][jx][x] = i1;
                    }else{
                        A[0][y][jx][x] = i2;
                    }
                }
            }
        }
        for(int jy=1; jy<logh; ++jy){
            int sz = 1<<(jy-1);
            for(int y=0; y+sz<h; ++y){
                for(int jx=0; jx<logw; ++jx){
                    for(int x=0; x<w; ++x){
                        point i1 = A[jy-1][y][jx][x];
                        point i2 = A[jy-1][y+sz][jx][x];
                        if(query(i1) < query(i2)){
                            A[jy][y][jx][x] = i1;
                        }else{
                            A[jy][y][jx][x] = i2;
                        }

                    }
                }
            }
        }
    }

    point pos_q(int x1, int x2, int y1, int y2){
        assert(A);
        int lenx = ilog2(x2 - x1);
        int leny = ilog2(y2 - y1);

        point idxs[] = {
                 A[leny][y1][lenx][x1],
                 A[leny][y2-(1<<leny)][lenx][x1],
                 A[leny][y1][lenx][x2-(1<<lenx)],
                 A[leny][y2-(1<<leny)][lenx][x2-(1<<lenx)]
                };
        point ret = idxs[0];
        for(int i=1; i<4; ++i){
            if(query(ret) > query(idxs[i])) ret = idxs[i];
        }
        return ret;

    }

    T val_q(int x1, int x2, int y1, int y2){
        point pos = pos_q(x1,x2,y1,y2);
        return v[pos.y][pos.x];
    }
};

rmq2d<long long> rmq;

set<tuple<int, int, int ,int>> cac;
vector<vector<long long>> v(MAX_DIM-5,vector<long long>(MAX_DIM-5,0));

long long ret = 0;
int nq = 0;

void go(int x1, int x2, int y1, int y2){
    if(x1 >= x2 || y1>=y2) return;

    if(!cac.insert(make_tuple(x1,y1,x2,y2)).second) return;
    ++nq;

    auto p = rmq.pos_q(x1, x2, y1, y2);
    long long cur = v[p.y][p.x]*(x2-x1)*(y2-y1);
    if(cur > ret){
        cout << x1 << "-" << x2 << ", " << y1 << "-" << y2 << " h=" << v[p.y][p.x] <<  " :" << cur << endl;
        ret = cur;
    }

    go(p.x+1, x2, y1, y2);
    go(x1, p.x, y1, y2);
    go(x1, x2, p.y+1, y2);
    go(x1, x2, y1, p.y);
}


int main(){
    int W = (int)v[0].size();
    int H=(int)v.size();
    for(int y=0; y<H;++y){
        for(int x=0; x<W; ++x){
            v[y][x] = rand()%10000;
        }
    }
    rmq = rmq2d<long long>(v);
    go(0,W, 0, H);
    cout << "nq:" << nq << endl;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM