web-dev-qa-db-ja.com

3Dで閉じ込められた雨水の最大量

2Dバージョンの古典的なアルゴリズムの質問は、通常、次のように説明されます。

各バーの幅が1である標高マップを表すn個の非負の整数が与えられた場合、雨が降った後にトラップできる水量を計算します。

たとえば、入力が与えられた場合

[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)ポイントでの標高がわかっており、目標は、形状に閉じ込められる可能性のある水の最大量を計算することです。どんな考えや参考文献も歓迎します。

20
SkyOasis

地形上の各ポイントについて、そのポイントから地形の境界までのすべてのパスを検討します。水位は、それらのパスのポイントの最大高さの最小値になります。それを見つけるには、わずかに修正されたダイクストラのアルゴリズムを実行し、境界から水位行列を埋める必要があります。

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
13
Anton

user3290797の「わずかに変更されたダイクストラアルゴリズム」は、ダイクストラのアルゴリズムよりもプリムのアルゴリズムに近いものです。最小全域木用語では、タイルごとに1つの頂点、外側に1つの頂点、および隣接する2つのタイルの最大高さに等しい重みを持つエッジ(外側の高さは「マイナス無限大」)を持つグラフを作成します。

このグラフの外側の頂点へのパスが与えられた場合、パス内のエッジの最大の重みは、そのパスに沿って逃げるために水が到達しなければならない高さです。最小スパニングツリーの関連するプロパティは、頂点のすべてのペアについて、スパニングツリーのパス内のエッジの最大重みがそれらの頂点間のすべてのパスの中で可能な最小値であるということです。したがって、最小全域木は、水の最も経済的な逃げ道を表し、水の高さは、1回のトラバースで線形時間で抽出できます。

ボーナスとして、グラフは平面であるため、最小スパニングツリーを計算するための線形時間アルゴリズムがあります。これは、交互のBoruvkaパスと簡略化で構成されます。これにより、PrimのO(n log n)実行時間が改善されます。

4
David Eisenstat

この問題は、グレースケール画像の形態学的流域の構築に非常に近いものです( http://en.wikipedia.org/wiki/Watershed_(image_processing) )。

1つのアプローチは次のとおりです(フラッディングプロセス):

  • 標高を上げてすべてのピクセルを並べ替えます。

  • 標高を上げ、集水域ごとのピクセルにラベルを割り当てることにより、段階的に作業します。

  • 新しい標高レベルの場合、新しいピクセルのセットにラベルを付ける必要があります。

    • ラベルの付いたネイバーがないものもあり、ローカルの最小構成を形成して、新しい集水域を開始します。
    • 同じラベルの隣人しかないものもあり、同様にラベルを付けることができます(集水域を拡張します)。
    • いくつかは異なるラベルの隣人を持っています。それらは特定の集水域に属しておらず、流域ラインを定義します。

水の量を計算できるようにするには、標準の流域アルゴリズムを拡張する必要があります。これを行うには、各流域の最大水位を決定し、すべてのピクセルの地面の高さを推定します。流域の水位は、その周囲の最も低い流域ピクセルの標高によって与えられます。

流域のピクセルを発見するたびに行動できます。隣接する流域にまだレベルが割り当てられていない場合、その流域は漏れることなく現在のレベルに耐えることができます。

2
Yves Daoust

この問題は、Priority-Floodアルゴリズムを使用して解決できます。あなたが探している特定の変種は、私の知る限り、文献にはありませんが、過去数十年にわたって何度も(そしてこの質問に答える他の人々によって)発見され、公開されています。

アルゴリズムとその変形のレビューペーパーを見つけることができます ここ 。その論文が発表されて以来、さらに高速なバリアントが発見され( link )、数兆個のセルのデータセットに対してこの計算を実行する方法( link )も発見されました。低/狭分割を選択的に破る方法について説明します ここ 。これらの論文のコピーが必要な場合は、私に連絡してください。

私はリポジトリを持っています ここ 上記のバリアントの多くがあります。追加の実装が見つかります ここ

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 ([email protected])

    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
2
Richard

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;
}
2
Jai Mahlawat
    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
0
Nancy

これが同じための簡単なコードです-

#include<iostream>
using namespace std;
int main()
{
   int n,count=0,a[100];
   cin>>n;
   for(int i=0;i<n;i++)
   {
       cin>>a[i];
   }
   for(int i=1;i<n-1;i++)
   {
       ///computing left most largest and Right most largest element of array;
       int leftmax=0;
       int rightmax=0;
       ///left most largest
       for(int j=i-1;j>=1;j--)
       {
           if(a[j]>leftmax)
           {
              leftmax=a[j];
           }
       }
       ///rightmost largest

       for(int k=i+1;k<=n-1;k++)
       {
           if(a[k]>rightmax)
           {
              rightmax=a[k];
           }
       }
       ///computing hight of the water contained-

       int x=(min(rightmax,leftmax)-a[i]);
       if(x>0)
       {
           count=count+x;
       }

   }
   cout<<count;
   return 0;
}
0
user8208104