假设您在起点s
,终点p
和q
描述的平面中设置了y
个水平线段 - 值。
我们可以假设p
和q
的所有值都是成对不同的,并且没有两个段重叠。
我想计算"下轮廓"该部分。
我们可以按s
对p
进行排序,并遍历每个细分j
。如果i
是&#34;有效&#34;细分和j->y < i->y
我们&#34;切换到&#34; j
(并输出相应的轮廓元素)。
但是,如果不存在j
这样的j
,我们可以做什么,我们会找到i->q < j->p
O(n log n)
。然后,我们需要切换到下一个更高的段&#34;。但我们怎么知道那个细分市场呢?我无法找到一种方法,使得生成的算法的运行时间为browser
。有什么想法吗?
答案 0 :(得分:1)
sweep line algorithm是解决问题的有效方法。正如Brian先前解释的那样,我们可以按x坐标对所有端点进行排序,并按顺序处理它们。这里要做的一个重要区别是我们按照增加起点的顺序对段的端点进行排序而不是段。
如果您想象一条垂直线在您的线段中从左向右扫过,您会注意到两件事:
这会立即产生一个观察结果:下部轮廓应该是段的列表。点列表不能提供足够的信息来定义轮廓,在某些x坐标(没有任何段)的情况下,轮廓可能是未定义的。
我们可以使用按片段的y位置排序的std::set
对活动集进行建模。按照增加x坐标的顺序处理端点。遇到左端点时,insert
该段。遇到正确的端点时,erase
该段。由于排序,我们可以在恒定时间内找到具有set::begin()
的最低y坐标的活动段。由于每个段只插入一次并擦除一次,因此维持活动集总共需要O(n log n)时间。
事实上,如果更容易,可以为与扫描线相交的每个线段仅维持std::multiset
个y坐标。
假设片段不重叠且具有不同的端点并非完全必要。重叠的段由有序的set
段和multiset
的y坐标处理。可以通过一次性考虑具有相同x坐标的所有端点来处理重合端点。
在这里,我假设没有零长度段(即点)来简化事情,尽管它们也可以用一些额外的逻辑来处理。
std::list<segment> lower_contour(std::list<segment> segments)
{
enum event_type { OPEN, CLOSE };
struct event {
event_type type;
const segment &s;
inline int position() const {
return type == OPEN ? s.sp : s.ep;
}
};
struct order_by_position {
bool operator()(const event& first, const event& second) {
return first.position() < second.position();
}
};
std::list<event> events;
for (auto s = segments.cbegin(); s != segments.cend(); ++s)
{
events.push_back( event { OPEN, *s } );
events.push_back( event { CLOSE, *s } );
}
events.sort(order_by_position());
// maintain a (multi)set of the y-positions for each segment that intersects the sweep line
// the ordering allows querying for the lowest segment in O(log N) time
// the multiset also allows overlapping segments to be handled correctly
std::multiset<int> active_segments;
bool contour_is_active = false;
int contour_y;
int contour_sp;
// the resulting lower contour
std::list<segment> contour;
for (auto i = events.cbegin(); i != events.cend();)
{
auto j = i;
int current_position = i->position();
while (j != events.cend() && j->position() == current_position)
{
switch (j->type)
{
case OPEN: active_segments.insert(j->s.y); break;
case CLOSE: active_segments.erase(j->s.y); break;
}
++j;
}
i = j;
if (contour_is_active)
{
if (active_segments.empty())
{
// the active segment ends here
contour_is_active = false;
contour.push_back( segment { contour_sp, current_position, contour_y } );
}
else
{
// if the current lowest position is different from the previous one,
// the old active segment ends here and a new active segment begins
int current_y = *active_segments.cbegin();
if (current_y != contour_y)
{
contour.push_back( segment { contour_sp, current_position, contour_y } );
contour_y = current_y;
contour_sp = current_position;
}
}
}
else
{
if (!active_segments.empty())
{
// a new contour segment begins here
int current_y = *active_segments.cbegin();
contour_is_active = true;
contour_y = current_y;
contour_sp = current_position;
}
}
}
return contour;
}
正如Brian所提到的,像std::priority_queue
这样的二进制堆也可用于维护活动集,并且往往优于std::set
,即使它不允许删除任意元素。您可以通过将段标记为已删除而不是删除它来解决此问题。然后,如果top()
是标记的细分,请重复删除priority_queue
。这可能最终会更快,但对于您的用例可能或不重要。
答案 1 :(得分:0)
首先按x坐标(起点和终点)对所有端点进行排序。迭代端点并保留活动段的所有y坐标的std::set
。到达起点时,将其y坐标添加到集合中,如果它是最低点,则“切换”到它;到达终点时,从集合中删除其y坐标,然后使用该集合重新计算最低y坐标。这总体上给出了O(n log n)解决方案。
平衡二叉搜索树(例如用于实现std::set
的二叉搜索树)通常具有较大的常数因子。您可以通过使用二进制堆(std::priority_queue
)而不是集合来加速此方法,并在根处使用最低的y坐标。在这种情况下,您无法删除非根节点,但是当您到达这样的结束点时,只需在数组中将该段标记为非活动状态。弹出根节点时,继续弹出,直到有一个尚未标记为非活动的新根节点。我认为这将是基于集合的方法的两倍,但你必须自己编写代码,看看,如果这是一个问题。