快速空间划分启发式?

时间:2015-08-03 18:37:49

标签: algorithm time-complexity computational-geometry heuristics

我有一个(子)空间,其中填充了N个线段。这些线段始终是凸多边形的一部分。它可能看起来像这样:

enter image description here

我想要做的是开发一种启发式方法,用于选择用于分割空间的线段。然后,所选分段的支撑线将分割空间。有两种启发式因素相互作用:

  1. 线段应均匀分割空间;完成后,子空间A中的子线段应与子空间B中的线段数一样多( 余额
  2. 线段的支撑线应与尽可能少的其他线段相交(分割)( 自由
  3. 一个例子:

    enter image description here

    蓝线:完全自由,非常糟糕的平衡。

    红线:非常糟糕的自由,平庸的平衡。

    绿线:自由度差,平衡性好。

    紫色线:良好的自由,平衡。

    在上面的示例中,组合启发式可能会选择紫色线。

    现在我可以循环遍历每个线段并将其与其他每个线段进行比较(查看它与哪些线段相交以及它们在每一侧的平衡程度)。但这需要O(N^2)次操作。我更喜欢在O(N log N)中运行的东西。

    关于O(N log N)算法的任何想法,它会遍历线段并给出分数?我的一个想法是对片段进行三次排序并形成一些象限:

    enter image description here

    象限中心给出了大多数线段的概念。因此,可以使用它们在该中心附近找到一个线段,并检查它的方向是否正确参考象限。不知何故。这样可以获得可靠的平衡分数。

    对于交叉点,我考虑过为段创建边界框并将它们排序到树中,从而可能加快交叉点估计?

    一些额外的提示(我的输入数据在很多时候看起来很像)

    1. 大多数片段是轴向的(纯X或Y方向)
    2. 与传播相比,大多数细分都很小。
    3. 感谢任何新的想法或见解 - 数据结构或策略的最小提示将有很大帮助!

1 个答案:

答案 0 :(得分:0)

我发现了一种适用于我的BSP树目的的启发式方法,这对于分析来说也非常有趣。在下面的代码中,我首先尝试使用AABB树来询问“此线与哪些线段相交”。但即使这个太慢了,所以最后我只是使用了一个昂贵的初始O(N^2)比较算法,它可以在BSP树建立时快速加速,使用一个有点聪明的观察!

  • 让每个BSP节点跟踪它仍留在子空间内的哪些线段(以及它必须从中选择下一个分割器)。
  • 让每个细分有四个与之关联的值:posCountnegCountintroducedsaved。如果它被拆分,那么它也有partner对另一个段的引用(否则它是null)。
  • 使用以下O(N^2)算法初始化根节点(即所有这些)的拆分器:

算法calcRelationCounts(splitters S)O(N^2)

for all splitters s in S
    s.posCount = s.negCount = s.introduced = s.saved = 0
    for all splitters vs in S
        if (s == vs) continue
        if vs is fully on the positive side of the plane of s
            s.posCount++
        else if vs is fully on the negative side of the plane of s
            s.negCount++
        else if vs intersects the plane of s
            s.negCount++, s.posCount++, s.introduced++
        else if vs is coplanar with s
            s.saved++
  • 对于仍有左侧分割符的每个节点,选择最大化以下内容的节点:

算法evaluate(...)其中treeDepth = floor(log2(splitterCountAtThisNode))O(1)

evaluate(posCount, negCount, saved, introduced, treeDepth) {
    float f;
    if (treeDepth >= EVALUATE_X2) {
        f = EVALUATE_V2;
    } else if (treeDepth >= EVALUATE_X1) {
        float r = treeDepth - EVALUATE_X1;
        float w = EVALUATE_X2 - EVALUATE_X1;
        f = ((w-r) * EVALUATE_V1 + r * EVALUATE_V2) / w;
    } else {
        f = EVALUATE_V1;
    }

    float balanceScore = -f * BALANCE_WEIGHT * abs(posCount - negCount);
    float freedomScore = (1.0f-f) * (SAVED_WEIGHT * saved - INTRO_WEIGHT * introduced);
    return freedomScore + balanceScore;
}

使用我的优化算法使用的以下幻数:

#define BALANCE_WEIGHT 437
#define INTRO_WEIGHT 750
#define SAVED_WEIGHT 562
#define EVALUATE_X1 3
#define EVALUATE_X2 31
#define EVALUATE_V1 0.0351639f
#define EVALUATE_V2 0.187508f
  • 使用此拆分器作为此节点的拆分器,将其命名为SEL。然后,将此节点上的所有拆分器划分为三个组positivesnegativesremnants

算法distributeSplitters()

for all splitters s at this node
    s.partner = null
    if s == SEL then add s to "remnants"
    else
        if s is fully on the positive side of SEL
            add s to "positives"
        else if s is fully on the negative side of SEL
            add s to "negatives
        else if s intersects SEL
            split s into two appropriate segments sp and sn
            sp.partner = sn, sn.partner = sp
            add sn to "negatives", sp to "positives" and s to "remnants"
        else if s coplanar with SEL
            add s to "remnants"

// the clever bit
if (positives.size() > negatives.size())
    calcRelationCounts(negatives)
    updateRelationCounts(positives, negatives, remnants)
else
    calcRelationCounts(positives)
    updateRelationCounts(negatives, positives, remnants)

add positives and negatives to appropriate child nodes for further processing

我在这里意识到的聪明之处在于,通常,尤其是具有上述启发式的前几个分裂,将产生非常不平衡的分裂(但非常自由)。问题是,当O(N^2) + O((N-n)^2)" + ...很小时,你会得到“n这是可怕的!相反,我意识到我们可以重新计算最小的分割O(n^2)而不是这样做这不错,然后简单地遍历每个位拆分器,从较小的拆分部分中减去计数,只需要O(Nn),这比O(N^2)要好得多!这是{{1}的代码}}:

算法updateRelationCounts()

updateRelationCounts()

我现在已经仔细测试了这一点,似乎逻辑是正确的,因为更新正确地修改了updateRelationCounts(toUpdate, removed, remnants) { for all splitters s in toUpdate for all splitters vs in removed, then remnants if vs has a partner if the partner intersects s s.posCount++, s.negCount++, s.introduced++ else if the partner is fully on the positive side of s s.posCount++ else if the partner is fully on the negative side of s s.negCount++ else if the partner is coplanar with s s.saved++ else if vs intersects s s.posCount--, s.negCount--, s.introduced-- else if vs is fully on the positive side of s s.posCount-- else if vs is fully on the negative side of s s.negCount-- else if vs is coplanar with s s.saved-- 等等,这样它们就像它们再次被硬计算一样!