我需要C中的空间索引

时间:2012-06-27 13:23:52

标签: c glib

我正在研究my gEDA fork并希望摆脱现有的简单的基于图块的系统 1 ,而不是真正的空间索引 2

有效找到 points 的算法是不够的:我需要找到非零范围的对象。考虑具有边界矩形的对象,它几乎捕获了索引中所需的细节级别。给定一个搜索矩形,我需要能够有效地找到其边界矩形在搜索矩形内部或相交的所有对象。

索引不能是只读的:gschem是一个原理图捕获程序,它的全部内容是在原理图中移动。所以事情将变得一团糟。因此,虽然我可以负担得起插入比搜索更昂贵,但它不能更昂贵,并且删除也必须既可能又合理便宜。但最重要的要求是渐近行为:如果不能是O(1),搜索应该是O(log n)。插入/删除最好应为O(log n),但O(n)可以。我绝对不想要任何东西> O(n)(每个动作;显然对于全对象操作是O(n log n))。

我有什么选择?我不够聪明地评估the various options。理想情况下会有一些C库可以为我做所有聪明的事情,但我会机械地实现一个算法,如果必须的话,我可能会或者可能不会完全理解。如果这有助于提出建议,gEDA会使用glib。

脚注:

1 标准gEDA将示意图划分为固定数量(当前为100个)“切片”,用于加速搜索边界矩形中的对象。这显然足以使大多数原理图足够快以进行搜索,但它的完成方式会导致其他问题:太多的函数需要指向事实上的全局对象的指针。瓷砖的几何形状也是固定的:只要通过平移(并可能缩放)到仅由一个瓷砖覆盖的区域,就可以完全打败这个瓷砖系统。

2 合理的答案是保留平铺系统的元素,但要修复它的弱点:教导它跨越整个空间,并在必要时进行细分。但我希望其他人在我自我决定这是最好的方式之前加上他们的两分钱。

4 个答案:

答案 0 :(得分:2)

您的需求听起来与游戏和物理模拟的碰撞检测算法中使用的非常相似。有几个开源C ++库可以在2-D (Box2D)或3-D (Bullet physics)中处理这个问题。虽然您的问题是针对C的,但您可能会发现他们的文档和实现很有用。

通常会将其拆分为two phases

  1. 快速宽相,通过轴对齐边界框(AABB)近似对象,并确定触摸或重叠的AABB对。
  2. 一个较慢的窄相,用于计算AABB接触或重叠的物体对的几何重叠点。
  3. 物理引擎也使用空间相干性来进一步减少被比较的对象对,但这种优化可能对您的应用程序没有帮助。

    宽相通常使用像Sweep and prune这样的O(N log N)算法实现。您可以通过将其与当前切片方法结合使用来加速这一过程(one of Nvidia's GPUGems描述了这种混合方法)。狭窄的阶段对于每一对都是相当昂贵的,并且可能对你的需求来说是过度的。 GJK algorithm通常用于此步骤中的凸对象,尽管对于更专业的情况存在更快的算法(例如:box / circle和box / sphere collisions)。

答案 1 :(得分:2)

点和线混合的良好数据结构将是R树或其衍生物之一(例如R * -Tree或Hilbert R-Tree)。鉴于您希望此索引是动态的和可序列化的,我认为使用SQLite's R*-Tree module将是一种合理的方法。

如果你能容忍C ++,libspatialindex有一个成熟而灵活的R-tree实现,它支持动态插入/删除和序列化。

答案 2 :(得分:0)

这听起来像一个非常适合四叉树的应用程序(假设你只对二维感兴趣。)quadtree是分层的(适合搜索),它的空间分辨率是动态的(允许更高的区域分辨率)需要它)。

我总是推出自己的四叉树,但这里有一个看似合理的图书馆:http://www.codeproject.com/Articles/30535/A-Simple-QuadTree-Implementation-in-C

答案 3 :(得分:-1)

很容易做到。做起来很难。听起来像我工作的问题,那里有一个巨大的最小值,最大值列表,并给出一个值,它必须返回多少个最小值,最大值对重叠该值。你只需要二维。因此,每个方向都有两棵树。然后对结果进行交集。这真的很快。

#include <iostream>
#include <fstream>
#include <map>

using namespace std;

typedef unsigned int UInt;

class payLoad {
public:
    UInt    starts;
    UInt    finishes;
    bool    isStart;
    bool    isFinish;
    payLoad ()
    {
        starts = 0;
        finishes = 0;
        isStart = false;
        isFinish = false;
    }
};

typedef map<UInt,payLoad> ExtentMap;

//==============================================================================
class Extents
{
    ExtentMap   myExtentMap;

public:

    void ReadAndInsertExtents ( const char* fileName )
    {
        UInt start, finish;
        ExtentMap::iterator EMStart;
        ExtentMap::iterator EMFinish;

        ifstream efile ( fileName);
        cout << fileName << " filename" << endl;

        while (!efile.eof()) {
            efile >> start >> finish;
            //cout << start << " start " << finish << " finish" << endl;
            EMStart = myExtentMap.find(start);
            if (EMStart==myExtentMap.end()) {
                payLoad pay;
                pay.isStart = true;
                myExtentMap[start] = pay;
                EMStart = myExtentMap.find(start);
                }
            EMFinish = myExtentMap.find(finish);
            if (EMFinish==myExtentMap.end()) {
                payLoad pay;
                pay.isFinish = true;
                myExtentMap[finish] = pay;
                EMFinish = myExtentMap.find(finish);
            }
            EMStart->second.starts++;
            EMFinish->second.finishes++;
            EMStart->second.isStart = true;
            EMFinish->second.isFinish = true;

//          for (EMStart=myExtentMap.begin(); EMStart!=myExtentMap.end(); EMStart++)
//              cout << "| key " << EMStart->first << " count " << EMStart->second.value << " S " << EMStart->second.isStart << " F " << EMStart->second.isFinish << endl;

        }

        efile.close();

        UInt count = 0;
        for (EMStart=myExtentMap.begin(); EMStart!=myExtentMap.end(); EMStart++)
        {
                count += EMStart->second.starts - EMStart->second.finishes;
                EMStart->second.starts = count +  EMStart->second.finishes;
        }

//      for (EMStart=myExtentMap.begin(); EMStart!=myExtentMap.end(); EMStart++)
//          cout << "||| key " << EMStart->first << " count " << EMStart->second.starts << " S " << EMStart->second.isStart << " F " << EMStart->second.isFinish << endl;

    }

    void ReadAndCountNumbers ( const char* fileName )
    {
        UInt number, count;
        ExtentMap::iterator EMStart;
        ExtentMap::iterator EMTemp;

        if (myExtentMap.empty()) return;

        ifstream nfile ( fileName);
        cout << fileName << " filename" << endl;

        while (!nfile.eof()) 
        {
            count = 0;
            nfile >> number;
            //cout << number << " number ";

            EMStart = myExtentMap.find(number);
            EMTemp = myExtentMap.end();

            if (EMStart==myExtentMap.end()) {           // if we don't find the number then create one so we can find the nearest number.
                payLoad pay;
                myExtentMap[ number ] = pay;
                EMStart = EMTemp = myExtentMap.find(number);
                if ((EMStart!=myExtentMap.begin()) && (!EMStart->second.isStart)) 
                {
                    EMStart--;
                }
            }

            if (EMStart->first < number) {
                while (!EMStart->second.isFinish) {
                    //cout << "stepped through looking for end - key" << EMStart->first << endl;
                    EMStart++;
                    }
                if (EMStart->first >= number) {
                    count = EMStart->second.starts;
                    //cout << "found " << count << endl;
                    }
            }
            else if (EMStart->first==number) {
                count = EMStart->second.starts;
                }

            cout << count << endl;

            //cout << "| count " << count << " key " << EMStart->first << " S " << EMStart->second.isStart << " F " << EMStart->second.isFinish<< " V " << EMStart->second.value << endl;

            if (EMTemp != myExtentMap.end()) 
            {
                myExtentMap.erase(EMTemp->first);
            }
        }
        nfile.close();      
    }

};

//==============================================================================

int main (int argc,  char* argv[]) {
    Extents exts;

    exts.ReadAndInsertExtents ( "..//..//extents.txt" );
    exts.ReadAndCountNumbers ( "..//../numbers.txt" );

    return 0;
}

范围测试文件是1.5mb:

0 200000
1 199999
2 199998
3 199997
4 199996
5 199995
....
99995 100005
99996 100004
99997 100003
99998 100002
99999 100001

数字文件就像:

102731
104279
109316
104859
102165
105762
101464
100755
101068
108442
107777
101193
104299
107080
100958
.....

即使从磁盘读取这两个文件,范围是1.5mb,数字是780k,并且真正大量的值和查找,这只需要几分之一秒。如果在记忆中它会很快闪电。