Algorithm 快速空间划分启发式算法?

Algorithm 快速空间划分启发式算法?,algorithm,time-complexity,computational-geometry,heuristics,Algorithm,Time Complexity,Computational Geometry,Heuristics,我有一个(子)空间,其中填充了N线段。这些线段始终是凸多边形的一部分。可能是这样的: 我想做的是开发一种启发式方法来选择分割空间的线段。然后,选定线段的支撑线将分割空间。有两个相互矛盾的启发因素: 线段应均匀分割空间;完成后,子空间A中的线段数应与子空间B中的线段数相同(平衡) 线段的支撑线应与尽可能少的其他线段相交(分割)(自由) 例如: 蓝线:完美的自由,非常糟糕的平衡 红线:非常糟糕的自由,平庸的平衡 绿线:自由度差,平衡性好 紫线:自由度好,平衡度好 在上面的示例中,组合启发式可能会

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

我想做的是开发一种启发式方法来选择分割空间的线段。然后,选定线段的支撑线将分割空间。有两个相互矛盾的启发因素:

  • 线段应均匀分割空间;完成后,子空间A中的线段数应与子空间B中的线段数相同(平衡
  • 线段的支撑线应与尽可能少的其他线段相交(分割)(自由
  • 例如:

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

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

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

    紫线:自由度好,平衡度好

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

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

    有没有关于循环通过线段并给它们打分的
    O(N logn)
    算法的想法?我的一个想法是对这些片段进行三次排序,形成一些象限:

    象限中心给出了大多数线段的位置。因此,可以使用它们来找到靠近这个中心的线段,并检查它相对于象限的方向是否正确。以某种方式这将给出一个良好的平衡分数

    对于交叉点,我考虑为线段创建边界框,并将它们排序到树中,这样可能会加快交叉点估计

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

  • 大多数线段为轴向(纯X或Y方向)
  • 与它们的分布相比,大多数细分市场都很小
  • 感谢您提出的任何新想法或见解-数据结构或策略的最小提示都会大有帮助

    解决方案 我发现了一种启发式方法,它非常适合我的BSP树,而且分析起来也非常有趣。在下面的代码中,我首先尝试使用AABB树来询问“这条线相交于哪些线段”。但即使这样也太慢了,所以最后我使用了一种昂贵的初始
    O(N^2)
    比较算法,该算法在BSP树构建时会快速加速,使用了一种稍微聪明的观察

    • 让每个BSP节点跟踪它在其子空间中仍然保留的线段(以及它必须从中选择下一个拆分器)
    • 让每个段都有四个与之关联的值:
      posCount
      negCount
      引入的
      保存的
      。让它也有一个对另一段的
      partner
      引用,以防它被拆分(否则它是
      null
    • 使用以下
      O(N^2)
      algo初始化根节点上的拆分器(即所有拆分器):
    算法
    calcreationcounts(拆分器)
    O(N^2)

    • 对于仍保留拆分器的每个节点,选择最大化以下内容的节点:
    算法
    evaluate(…)
    其中
    treeDepth=floor(log2(splitterCountAtThisNode))
    O(1)

    使用我的优化算法使用的以下神奇数字:

    #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。然后,将此节点上的所有拆分器分为三组
      正片
      负片
      残片
    算法
    distributespliters()

    我在这里意识到的一件聪明的事情是,通常,特别是使用上述启发式的前几次拆分,会产生非常不平衡的拆分(但非常自由)。问题是你会得到“
    O(N^2)+O((N-N)^2)”+…
    ,这在
    N
    很小的时候是很可怕的!相反,我意识到,不必这样做,我们可以努力重新计算最小的分割,它取
    O(n^2)
    ,这并不坏,然后简单地迭代每个位分割分割器,从较小的分割部分减去计数,这只取
    O(Nn)
    ,这比
    O(n^2)
    好得多!以下是
    updateRelationCounts()
    的代码:

    算法
    updateRelationCounts()


    我现在已经仔细地测试过了,逻辑似乎是正确的,因为更新正确地修改了
    posCount
    等,因此它们与再次进行硬计算时相同

    看起来像是二进制空间分区树问题。别忘了这方面的任何启发。kd树有一种常见的方法,称为SAH(表面积启发式)。这可以让你了解成本函数是如何在细分过程中找到最优解的。是的,这是用于BSP构造的。不过,这里的数据结构很有用,谢谢!在你的算法中。你在乎线路方向吗?如果是BSP构造,则感觉后续行的方向不应彼此独立拾取。您可能希望下一行或多或少与上一个选项垂直。对吗?
    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
    
    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
    
    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--