【问题标题】:Non-recursive algorithm to walk the edges of a Voronoi diagram with boost::polygon使用 boost::polygon 遍历 Voronoi 图边缘的非递归算法
【发布时间】:2013-11-21 13:52:32
【问题描述】:

什么是使用boost而不递归遍历Voronoi图边缘的好算法?

我知道它必须检查单元格中的无限边缘,然后检查其邻居并从那里重复,但我更喜欢不需要递归的方法,因为我正在处理大量数据集。

没有递归这可能吗?

编辑,以获得更多说明:

这是一种获取所有边缘单元格的方法:

voronoi_diagram vd;
boost::polygon::construct_voronoi(in.begin(), in.end(), &vd);

std::vector<const voronoi_diagram::cell_type *> edge_cells;

for(const voronoi_diagram::edge_type & e : vd.edges())
  if (e.is_infinite())
    edge_cells.push_back(e.cell());

上述方法的问题在于它不会以任何特定的顺序遍历边缘单元格,例如顺时针。

递归实现会做类似于这个(仓促编写和未经测试的)代码的事情:

bool findNext(const voronoi_diagram::cell_type * c,
              std::list<const voronoi_diagram::cell_type *> & list)
{
  const voronoi_diagram::edge_type * e = c->incident_edge();
  do
  {
    // Follow infinite edges, adding cells encountered along the way
    if (e->is_infinite() && e->twin()->cell() != list.front() &&
      e->twin()->cell() != list.back())
    {
      list.push_back(c);
      return findNext(e->twin()->cell(), list);
    }
    else if (e->twin()->cell() == list.front())
    {
      list.push_back(c);
      return true; // we reached the starting point, return
    }      
    e = e->next();
  } while (e != c->incident_edge());
  return false;
}
// ...
std::list<const voronoi_diagram::cell_type *> edge_cells;
// ...
for(const voronoi_diagram::edge_type & e : vd.edges())
{
  // find first infinite edge
  if (e.is_infinite())
  {
    if (findNext(e.cell(), edge_cells))
      break;
    else
      edge_cells.clear();
  }
}

这将遍历 Voronoi 图的边缘,直到它回溯到第一个单元格,然后停止,一直填充堆栈。

非递归实现将对第二个示例进行建模,以顺时针或逆时针顺序生成边缘单元列表,而不使用递归。

【问题讨论】:

  • 您是否尝试过在循环中使用迭代而不是递归?
  • 是的,嵌套循环,它变得如此复杂,与递归方法相比几乎不可行。这就是为什么我想看到一个关于这个主题的新观点,也许我做错了。
  • @voodooattack:如果您阅读了我的答案,我清楚地写了它是关于凸包的。也许你可以分享输出的图像?

标签: c++ algorithm recursion voronoi boost-polygon


【解决方案1】:

findNext 中只有一个递归调用,它是return findNext(...),所以可以应用所谓的tail-call 优化。您的编译器可能会在 -O3 处执行此操作。但如果你不相信编译器会这样做,你可以手动完成。下面是转换后的函数,不再递归:

bool findNext(const voronoi_diagram::cell_type * c, 
          std::list<voronoi_diagram::cell_type *> & list)
{
  const voronoi_diagram::edge_type * e = c->incident_edge();
  bool justCalled; // true when we repalce the tail call
  do
  {
    justCalled = false;
    // Follow infinite edges, adding cells encountered along the way
    if (e->is_infinite() && e->twin()->cell() != list.front() &&
        e->twin()->cell() != list.back())
    {
      list.push_back(c);
      c = e->twin()->cell();    // reassigns function argument
      e =  c->incident_edge();  // replay the initiaization (before do loop)
      justCalled = true;        // force the loop to continue
      continue;                 // jump to start of loop
      // everything happens as if we called findNext(e->twin()->cell(), list);
    else if (e->twin()->cell() == list.front())
    {
      list.push_back(c);
      return true; // we reached the starting point, return
    } 
    e = e->next();
  } while (justCalled || e != c->incident_edge());
  return false;
}

这个函数和你写的那个函数是等价的,所以你可以用同样的方法来使用它,而且你确定不涉及递归。 bool 标志是必要的,因为 continue 跳转到循环的测试而不是它的主体 see here,因此当我们更改参数并调用 continue 时,测试甚至可能在循环开始之前失败。

这是一种通用技术,并非特定于图遍历,而是适用于所有递归算法。当然,如果您有很多函数参数和大量代码,则转换是繁重的,但在这种情况下,我认为这是一个很好的匹配。

在递归不是尾调用的更复杂的情况下,您仍然可以通过维护自己的堆栈来“取消递归”任何函数。这样做的好处是,用优先级 fifo 替换堆栈结构可能会以比递归可以(轻松)实现的方式更微妙的方式改变遍历顺序。

【讨论】:

  • 我更新了代码以匹配您所做的更改。我看到你在 else if 中添加了 push_back,错误行为可能来自那里。
  • 注意:原始答案无效,因为continue 运行导致过早退出的循环条件。现在已修复。
  • 伙计,我发放赏金的速度太慢了。但是很好地提出答案:)。
  • @Phpdna 排序可能有助于提高性能,但 voodooattack 已经接受了他想要的答案。我也正在做赏金,因为我认为这是一个有趣的问题,没有得到足够的关注。顺便说一句,否决票不是我:​​P
猜你喜欢
  • 2011-11-08
  • 1970-01-01
  • 2018-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多