有效百分位数查找的数据结构?

时间:2012-12-21 22:20:24

标签: algorithm math data-structures statistics percentile

假设您有大量的键/值对,其中值是一些任意实数。您有兴趣创建支持以下操作的数据结构:

  • 插入,为集合添加新的键/值对
  • 删除,从集合中删除键/值对
  • Percentile ,用于说明与给定密钥相关联的值的百分位数,
  • Tell-Percentile ,接受百分位数并返回其值至少为给定百分位数的最低值的键。

例如,可以使用此数据结构来有效地确定给定学生在接收全国测试分数流时的百分位数,或识别具有异常好坏服务质量的医院。

有没有办法让这些操作有效运行(比如,次线性时间?)

2 个答案:

答案 0 :(得分:11)

实现此数据结构的一种可能方法是使用order statistic treehash table的混合。

订单统计树是一种平衡二叉搜索树,除了正常的二叉搜索树操作外,还支持另外两个操作:

  • 等级(键),它返回树中元素的数量小于给定元素,
  • 选择(k),返回树中第k个最小元素。

可以通过使用在旋转期间保留的额外信息来扩充正常的平衡二叉搜索树(例如,red/black treeAVL tree)来构建订单统计树。通过这种方式,可以使订单统计树上的所有正常BST操作在O(log n)时间内运行,额外操作也在O(log n)时间内运行。

现在,让我们假设你纯粹存储价值分数,而不是关键/百分位分数。在这种情况下,如下实现百分位查找将非常简单。将所有值存储在订单统计树中。要确定给定值的百分位数分数,请使用订单统计信息树上的排名操作来查找该值显示的索引。这给出了一个数字,范围从0到n - 1(其中n是树中元素的数量),表示该分数在订单统计树中的位置。然后,您可以将该数字乘以99 /(n - 1),以根据需要获得在0到99范围内运行的值的百分位数。

要确定大于某个百分位数的最低值,您可以使用选择操作,如下所示。给定0到99之间的百分位数,多个百分位数99 /(n - 1)得到0和n - 1之间的实数,包括0和n - 1。取该数字的上限会产生0到n - 1(包括0和n - 1)范围内的自然数。使用订单统计树上的选择操作,可用于查找范围内等于或高于给定百分位数的第一个值。

但是,这些操作假设我们在数据结构中具有纯值,而不是键/值对。为了使这个操作适用于键/值对,我们将按如下方式扩充我们的数据结构:

  1. 我们将在每个节点中存储键/值对,而不仅仅是存储值。订单统计树将纯粹按其值对密钥/值对进行排序,密钥作为卫星数据携带。
  2. 我们将存储一个将键映射到其关联值的辅助哈希表。
  3. 这两项更改可以为我们的数据结构实现所需的功能。为了使数据结构按密钥进行百分位查找,我们首先使用给定的密钥查询哈希表以查找其关联值。然后,我们对值进行百分位查找,如前所述。为了使数据结构告诉我们一个值为第一个等于或高于给定百分位数的密钥,我们在订单统计树上执行正常的查找百分位操作,如上所述,然后查找与给定值相关联的密钥。

    如果我们假设哈希表使用链式散列,那么每个操作所需的时间如下:

    • 插入:O(log n)时间将值/密钥对插入订单统计树,加上O(1)摊销时间以将密钥/值对插入哈希表。总时间为O(log n)摊销。
    • 删除:O(log n)时间从订单统计树中删除值/密钥对,加上(1)从哈希表中删除密钥/值对的分摊时间。总时间为O(log n)摊销。
    • 百分位数:O(1)预计查找与密钥关联的值的时间,O(log n)时间执行排名操作,以及O( 1)将等级映射到百分位数的额外时间。总时间为预期的O(log n)。
    • 查找百分位:将百分位数映射到排名所需的时间为O(1),执行选择操作所需的时间为O(log n)。总时间是O(log n)最坏情况。

    希望这有帮助!

答案 1 :(得分:2)

有一种简单而高效的可能性:

如果您只能在最终填满的学生结构中搜索百分位数,那么:

当您不知道元素的数量时,使用ArrayList动态构建 如果你知道它们,那么直接从数组开始,否则从动态数组创建数组。 (例如java中的ArrayList)。

插入:不需要,最后添加,排序一次 删除:不是必要的,如果你可以忍受。
tell-percentile :更简单:非常接近的东西:element [length * percentile]:O(1)

实际上,数组方法比平衡树方法快得多,至少在java中, 当你的应用程序可以建立一次数组(例如每日学生评估,每天建立)

我使用自编写的ArrayListInt实现了上面的(my)算法,它与ArrayList相同但使用原始类型(double,int),而不是对象类型。当所有数据都已被读取时,我对它进行了一次排序。

此外,您想要键值:
我只想添加一个树形图(平衡树)。现在,如果TreeMap和附加百分位数组有意义,那就有点怀疑了:那取决于你搜索的频率,以及内存使用与搜索时间的关系。

<强>更新

结果:treeset vs sorted array(动态构建数组,然后最后排序一次:

num elements: 1000 treeSet: 4.55989 array=0.564159
num elements: 10000 treeSet: 2.662496 array=1.157591
num elements: 100000 treeSet: 31.642027 array=12.224639
num elements: 1000000 treeSet: 1319.283703 array=140.293312
num elements: 10000000 treeSet: 21212.307545 array=3222.844045

这些元素(1e7)现在已接近极限(1GB堆空间),在下一步中内存将耗尽(已在1e7发生,但在树集之后清理内存,测量1e7的运行工作,太

缺少的是搜索时间,但带有binsearch的排序数组只能通过哈希表来打败

<强>最后: 如果你可以建立一次学生集,例如每天,那么使用数组方法可以提供更简单的百分位数搜索。