3D中被困雨水的最大容积

时间:2014-02-16 23:08:53

标签: algorithm graphics computational-geometry

2D版本中的经典算法问题通常被描述为

给定n个非负整数,表示每个条的宽度为1的高程图,计算下雨后能够捕获的水量。

例如,给定输入

[0,1,0,2,1,0,1,3,2,1,2,1] 

返回值为

6

enter image description here

我用来解决上述2D问题的算法是

int trapWaterVolume2D(vector<int> A) {
    int n = A.size();
    vector<int> leftmost(n, 0), rightmost(n, 0);

    //left exclusive scan, O(n), the highest bar to the left each point
    int leftMaxSoFar = 0;
    for (int i = 0; i < n; i++){
        leftmost[i] = leftMaxSoFar;
        if (A[i] > leftMaxSoFar) leftMaxSoFar = A[i];
    }


    //right exclusive scan, O(n), the highest bar to the right each point
    int rightMaxSoFar = 0;
    for (int i = n - 1; i >= 0; i--){
        rightmost[i] = rightMaxSoFar;
        if (A[i] > rightMaxSoFar) rightMaxSoFar = A[i];
    }

    // Summation, O(n)
    int vol = 0;
    for (int i = 0; i < n; i++){
        vol += max(0, min(leftmost[i], rightmost[i]) - A[i]);
    }
    return vol;
}

我的问题是如何使上述算法可扩展到问题的3D版本,以计算在真实世界3D地形中捕获的最大水量。即实施

int trapWaterVolume3D(vector<vector<int> > A);

示例图:

enter image description here

我们知道每个(x,y)点的高程,目标是计算可以在形状中捕获的最大水量。欢迎任何想法和参考。

7 个答案:

答案 0 :(得分:13)

对于地形上的每个点,考虑从该点到地形边界的所有路径。水位将是这些路径点的最大高度的最小值。为了找到它,我们需要执行略微修改的Dijkstra算法,从边界开始填充水位矩阵。

For every point on the border set the water level to the point height
For every point not on the border set the water level to infinity
Put every point on the border into the set of active points
While the set of active points is not empty:
    Select the active point P with minimum level
    Remove P from the set of active points
    For every point Q adjacent to P:
        Level(Q) = max(Height(Q), min(Level(Q), Level(P)))
        If Level(Q) was changed:
            Add Q to the set of active points

答案 1 :(得分:4)

user3290797的“略微修改的Dijkstra算法”比Dijkstra更接近Prim的算法。在最小生成树术语中,我们准备一个图表,每个图块有一个顶点,外部有一个顶点,权重等于两个相邻图块的最大高度的边缘(外部有高度“减去无穷大”)。

如果此图中的路径指向外部顶点,则路径中边缘的最大权重是水必须达到的高度才能沿着该路径逃逸。最小生成树的相关属性是,对于每对顶点,生成树中的路径中的边的最大权重是这些顶点之间的所有路径中可能的最小权重。因此,最小生成树描述了水的最经济的逃生路径,并且可以通过一次遍历在线性时间内提取水高。

作为奖励,由于图表是平面的,因此有一种用于计算最小生成树的线性时间算法,包括交替的Boruvka通道和简化。这改善了Prim的O(n log n)运行时间。

答案 2 :(得分:2)

这个问题非常接近于灰度图像(http://en.wikipedia.org/wiki/Watershed_(image_processing))的形态分水岭的构造。

一种方法如下(洪水过程):

  • 通过增加高程对所有像素进行排序。

  • 逐步增加工作,为每个集水盆地的像素分配标签。

  • 对于新的高程级别,您需要标记一组新的像素:

    • 有些人没有贴标签 邻居,他们形成了一个当地的最低配置,并开始一个新的集水盆。
    • 有些人只有相同标签的邻居,他们可以类似地标记(他们延伸集水盆)。
    • 有些人有不同标签的邻居。它们不属于特定的流域,它们定义了分水岭线。

您需要增强标准分水岭算法才能计算出水量。你可以通过确定每个盆地的最高水位并推断出每个像素的地面高度来做到这一点。盆地中的水位由其周围最低流域像素的高度给出。

每次发现分水岭像素时,您都可以采取行动:如果尚未为相邻水池分配水平,则该水池可以保持当前水平而不会泄漏。

答案 3 :(得分:2)

为了实现3D中的出水问题,即计算被困雨水的最大容量,您可以这样做:

#include<bits/stdc++.h>
using namespace std;

#define MAX 10

int new2d[MAX][MAX];
int dp[MAX][MAX],visited[MAX][MAX];


int dx[] = {1,0,-1,0};
int dy[] = {0,-1,0,1};


int boundedBy(int i,int j,int k,int in11,int in22)
{
    if(i<0 || j<0 || i>=in11 || j>=in22)
        return 0;

    if(new2d[i][j]>k)
        return new2d[i][j];

    if(visited[i][j])   return INT_MAX;

    visited[i][j] = 1;

    int r = INT_MAX;
    for(int dir = 0 ; dir<4 ; dir++)
    {
        int nx = i + dx[dir];
        int ny = j + dy[dir];
        r = min(r,boundedBy(nx,ny,k,in11,in22));
    }
    return r;
}

void mark(int i,int j,int k,int in1,int in2)
{
    if(i<0 || j<0 || i>=in1 || j>=in2)
        return;

    if(new2d[i][j]>=k)
        return;

    if(visited[i][j])   return ;

    visited[i][j] = 1;

    for(int dir = 0;dir<4;dir++)
    {
        int nx = i + dx[dir];
        int ny = j + dy[dir];
        mark(nx,ny,k,in1,in2);
    }
    dp[i][j] = max(dp[i][j],k);
}

struct node
{
    int i,j,key;
    node(int x,int y,int k)
    {
        i = x;
        j = y;
        key = k;
    }
};

bool compare(node a,node b)
{
    return a.key>b.key;
}
vector<node> store;

int getData(int input1, int input2, int input3[])
 {

    int row=input1;
    int col=input2;
    int temp=0;
    int count=0;

        for(int i=0;i<row;i++)
        {
            for(int j=0;j<col;j++)
            {
                if(count==(col*row)) 
                break;
            new2d[i][j]=input3[count];
            count++;
            }
        }

    store.clear();
    for(int i = 0;i<input1;i++)
        {
            for(int j = 0;j<input2;j++)
            {
                store.push_back(node(i,j,new2d[i][j]));
            }
        }
     memset(dp,0,sizeof(dp));

        sort(store.begin(),store.end(),compare);

       for(int i = 0;i<store.size();i++)
        {
            memset(visited,0,sizeof(visited));

            int aux = boundedBy(store[i].i,store[i].j,store[i].key,input1,input2);
            if(aux>store[i].key)
            {

                 memset(visited,0,sizeof(visited));
                 mark(store[i].i,store[i].j,aux,input1,input2);
            }

        }

        long long result =0 ;

        for(int i = 0;i<input1;i++)
        {

            for(int j = 0;j<input2;j++)
            {
                result = result + max(0,dp[i][j]-new2d[i][j]);
            }
        }

      return result;

 }


int main()
{
    cin.sync_with_stdio(false);
    cout.sync_with_stdio(false);
       int n,m;
        cin>>n>>m;
       int inp3[n*m];
        store.clear();

            for(int j = 0;j<n*m;j++)
            {
                cin>>inp3[j];

            }

    int k = getData(n,m,inp3);
       cout<<k;

    return 0;
}

答案 4 :(得分:2)

使用Priority-Flood算法可以解决此问题。在过去的几十年里,它已被发现和出版多次(其他人也回答了这个问题),尽管根据我的知识,你所寻找的具体变体并不是在文献中。

您可以找到该算法及其变体here的评论文章。自该论文发表以来,发现了一种更快的变体(link),以及对数万亿个细胞(link)的数据集进行计算的方法。讨论了一种选择性地破坏低/窄除法的方法here。如果您想要任何这些文件的副本,请与我联系。

我有一个存储库here,其中包含许多上述变体;可以找到其他实现here

使用RichDEM库计算音量的简单脚本如下:

#include "richdem/common/version.hpp"
#include "richdem/common/router.hpp"
#include "richdem/depressions/Lindsay2016.hpp"
#include "richdem/common/Array2D.hpp"

/**
  @brief  Calculates the volume of depressions in a DEM
  @author Richard Barnes (rbarnes@umn.edu)

    Priority-Flood starts on the edges of the DEM and then works its way inwards
    using a priority queue to determine the lowest cell which has a path to the
    edge. The neighbours of this cell are added to the priority queue if they
    are higher. If they are lower, then they are members of a depression and the
    elevation of the flooding minus the elevation of the DEM times the cell area
    is the flooded volume of the cell. The cell is flooded, total volume
    tracked, and the neighbors are then added to a "depressions" queue which is
    used to flood depressions. Cells which are higher than a depression being
    filled are added to the priority queue. In this way, depressions are filled
    without incurring the expense of the priority queue.

  @param[in,out]  &elevations   A grid of cell elevations

  @pre
    1. **elevations** contains the elevations of every cell or a value _NoData_
       for cells not part of the DEM. Note that the _NoData_ value is assumed to
       be a negative number less than any actual data value.

  @return
    Returns the total volume of the flooded depressions.

  @correctness
    The correctness of this command is determined by inspection. (TODO)
*/
template <class elev_t>
double improved_priority_flood_volume(const Array2D<elev_t> &elevations){
  GridCellZ_pq<elev_t> open;
  std::queue<GridCellZ<elev_t> > pit;
  uint64_t processed_cells = 0;
  uint64_t pitc            = 0;
  ProgressBar progress;

  std::cerr<<"\nPriority-Flood (Improved) Volume"<<std::endl;
  std::cerr<<"\nC Barnes, R., Lehman, C., Mulla, D., 2014. Priority-flood: An optimal depression-filling and watershed-labeling algorithm for digital elevation models. Computers & Geosciences 62, 117–127. doi:10.1016/j.cageo.2013.04.024"<<std::endl;

  std::cerr<<"p Setting up boolean flood array matrix..."<<std::endl;
  //Used to keep track of which cells have already been considered
  Array2D<int8_t> closed(elevations.width(),elevations.height(),false);

  std::cerr<<"The priority queue will require approximately "
           <<(elevations.width()*2+elevations.height()*2)*((long)sizeof(GridCellZ<elev_t>))/1024/1024
           <<"MB of RAM."
           <<std::endl;

  std::cerr<<"p Adding cells to the priority queue..."<<std::endl;

  //Add all cells on the edge of the DEM to the priority queue
  for(int x=0;x<elevations.width();x++){
    open.emplace(x,0,elevations(x,0) );
    open.emplace(x,elevations.height()-1,elevations(x,elevations.height()-1) );
    closed(x,0)=true;
    closed(x,elevations.height()-1)=true;
  }
  for(int y=1;y<elevations.height()-1;y++){
    open.emplace(0,y,elevations(0,y)  );
    open.emplace(elevations.width()-1,y,elevations(elevations.width()-1,y) );
    closed(0,y)=true;
    closed(elevations.width()-1,y)=true;
  }

  double volume = 0;

  std::cerr<<"p Performing the improved Priority-Flood..."<<std::endl;
  progress.start( elevations.size() );
  while(open.size()>0 || pit.size()>0){
    GridCellZ<elev_t> c;
    if(pit.size()>0){
      c=pit.front();
      pit.pop();
    } else {
      c=open.top();
      open.pop();
    }
    processed_cells++;

    for(int n=1;n<=8;n++){
      int nx=c.x+dx[n];
      int ny=c.y+dy[n];
      if(!elevations.inGrid(nx,ny)) continue;
      if(closed(nx,ny))
        continue;

      closed(nx,ny)=true;
      if(elevations(nx,ny)<=c.z){
        if(elevations(nx,ny)<c.z){
          ++pitc;
          volume += (c.z-elevations(nx,ny))*std::abs(elevations.getCellArea());
        }
        pit.emplace(nx,ny,c.z);
      } else
        open.emplace(nx,ny,elevations(nx,ny));
    }
    progress.update(processed_cells);
  }
  std::cerr<<"t Succeeded in "<<std::fixed<<std::setprecision(1)<<progress.stop()<<" s"<<std::endl;
  std::cerr<<"m Cells processed = "<<processed_cells<<std::endl;
  std::cerr<<"m Cells in pits = "  <<pitc           <<std::endl;

  return volume;
}

template<class T>
int PerformAlgorithm(std::string analysis, Array2D<T> elevations){
  elevations.loadData();

  std::cout<<"Volume: "<<improved_priority_flood_volume(elevations)<<std::endl;

  return 0;
}

int main(int argc, char **argv){
  std::string analysis = PrintRichdemHeader(argc,argv);

  if(argc!=2){
    std::cerr<<argv[0]<<" <Input>"<<std::endl;
    return -1;
  }

  return PerformAlgorithm(argv[1],analysis);
}

应该直接适应你正在使用的任何2d数组格式

在伪代码中,以下内容与上述内容相同:

Let PQ be a priority-queue which always pops the cell of lowest elevation
Let Closed be a boolean array initially set to False
Let Volume = 0
Add all the border cells to PQ.
For each border cell, set the cell's entry in Closed to True.
While PQ is not empty:
  Select the top cell from PQ, call it C.
  Pop the top cell from PQ.
  For each neighbor N of C:
    If Closed(N):
      Continue
    If Elevation(N)<Elevation(C):
      Volume += (Elevation(C)-Elevation(N))*Area
      Add N to PQ, but with Elevation(C)
    Else:
      Add N to PQ with Elevation(N)
    Set Closed(N)=True

答案 5 :(得分:0)

以下是相同的简单代码 -

df = read.table(text="          A        B
      1. 21      2011
                2. 21      2012
                3. 21      2011
                4. 22      2013
                5. 22      2011
                6. 23      2012
                7. 23      2011
                8. 23      2012
                9. 23      2014",header=T)

library(dplyr)
df = df %>% group_by(A,B) %>% mutate(n=n()) %>% ungroup %>% complete(A, B, fill = list(n = 0)) %>% as.data.frame 
df<- reshape(df,timevar="B",idvar="A",direction="wide")
colnames(df)<- gsub("n\\.","",colnames(df))

答案 6 :(得分:0)

    class Solution(object):
def trapRainWater(self, heightMap):
    """
    :type heightMap: List[List[int]]
    :rtype: int
    """
    m = len(heightMap)
    if m == 0:
        return 0
    n = len(heightMap[0])
    if n == 0:
        return 0
    visited = [[False for i in range(n)] for j in range(m)]
    from Queue import PriorityQueue
    q = PriorityQueue()
    for i in range(m):
        visited[i][0] = True
        q.put([heightMap[i][0],i,0])
        visited[i][n-1] = True
        q.put([heightMap[i][n-1],i,n-1])
    for j in range(1, n-1):
        visited[0][j] = True
        q.put([heightMap[0][j],0,j])
        visited[m-1][j] = True
        q.put([heightMap[m-1][j],m-1,j])
    S = 0
    while not q.empty():
        cell = q.get()
        for (i, j) in [(1,0), (-1,0), (0,1), (0,-1)]:
            x = cell[1] + i
            y = cell[2] + j
            if x in range(m) and y in range(n) and not visited[x][y]:
                S += max(0, cell[0] - heightMap[x][y])   # how much water at the cell
                q.put([max(heightMap[x][y],cell[0]),x,y])
                visited[x][y] = True
    return S