当桶排序中密钥的分布稀疏时,可能会有很多空桶。 我们如何有效地检索排序列表(即,实现连接操作)?
我们希望实现基于存储桶的优先级队列,但搜索第一个非空存储桶可能需要很长时间。所以我们想知道一种更聪明的方法。
例如,如果我们得到一个包含数百万的10,1000,50000,100000,6400,000,10000000等的列表,我们如何使用存储桶排序检索已排序的列表?
另一个更难的例子是,1,100,101,......,999,1000,100000,100001,...... 999999,1000000,100000000,100000001,......,199999999。
可能更难的情况是某些细分市场中的分布密集,但细分市场之间可能存在巨大差距。
答案 0 :(得分:0)
您的申请必须特别。如果桶很稀疏,人们可能会认为每个桶平均只有一个或两个项目。如果是这样,那么存储桶排序对你没有任何好处 - 只需将这些项目放入堆中即可。
如果桶不是那么稀疏,即如果桶的数量是< =项目数的几倍,那么桶的排序就足够了 - 按顺序遍历桶并且成本将是O( N)项目数量。
如果每个非空桶有很多项目,每个项目有很多桶,那么你可能想解释一下你的用例,但是当我在过去看到这个时,将每个桶插入堆中是合理的当它变得非空时。
答案 1 :(得分:0)
您问题的简单答案是“不是没有额外的数据结构来跟踪哪些存储桶有项目。”
有多种方法可以进行存储桶排序。 “最佳”很大程度上取决于键的范围,项目数和唯一项的数量。如果你的范围是0到1,000,000并且你知道你有50%的独特性,那么单个阵列的1,000,000个桶很容易使用,你不会浪费太多的空间,你不要浪费大量时间跳过空桶。
但是,如果你说的是人口稀少的数十亿的范围,你最终会浪费大量的内存和相当多的时间来跳过空桶。在极端情况下,您甚至无法分配足够大的数组来覆盖整个范围。
实现存储桶排序的另一种常用方法是使用哈希映射字典。这个想法是:
initialize empty hash map
for each item in list
if key already in hash map
add item to that bucket
else
create new bucket in hash map
当然,一旦你完成填充,你必须按键对存储桶进行排序,但是对几千个(如果那样)存储桶进行排序需要花费很少的时间。而且你最终不会在空桶上浪费千兆字节的内存。
当我构建基于桶的优先级队列时,我使用了字典方法。我维护了一个由索引键入的字典,并将每个项目添加到正确的存储桶中。我还维护了一个简单的桶二进制堆。因此,向堆中添加项目变为:
if item.key exists in dictionary
dictionary[item.key].add(item) // adds item to bucket
else
{
dictionary.add(item.key, item) // creates a new bucket
heap.push(dictionary[item.key]) // pushes the bucket onto the heap
}
从堆中删除项目变为:
bucket = heap.peek()
item = bucket.getFirst()
if (bucket.count() == 0)
{
// bucket is empty. Remove from heap and from dictionary
heap.pop()
dictionary.remove(item.key)
}
return item
这表现得很好。因为我的密钥稀疏且桶很多,所以堆本身很少有任何活动。大多数活动涉及向已经在堆中的桶中添加内容和从中删除内容。堆运动的唯一时间是清空铲斗或添加新铲斗时。所以平均,插入和删除都非常接近O(1)。
这对我来说效果很好,因为我的按键范围非常大(10个字符的字母数字),数以亿计的单个项目的数量,或数十亿,但任何时候使用的唯一密钥的数量都在数千人。字典间接有一些轻微的开销,但这远远超过了使用几千个而不是数亿个项目的节省。