对于许多相同的键,最有效的排序算法?

时间:2008-12-09 21:00:07

标签: performance algorithm optimization sorting hash

在数组中将相同项目组合在一起的最有效算法是什么,给出以下内容:

  1. 几乎所有项目都重复了几次。
  2. 这些项目不一定是整数或其他同样简单的东西。键的范围甚至没有明确定义,更不用说小了。实际上,键可以是任意结构。这排除了最简单的计数排序形式。
  3. 我们关心渐近和非渐近属性,有时n可能很小。但是,当n很小时,性能仍然很重要,因为在数百万个小数据集的循环中,这个函数可能被称为数百万次。这排除了任何昂贵的散列函数或使用需要执行大量内存分配的复杂数据结构。
  4. 只要所有相同的项目组合在一起,数据就可以按任意顺序排序。
  5. 如果这令人困惑,这是一个例子,假设这样的函数被命名为groupIdentical:

    uint[] foo = [1,2,3,2,1,5,4,5];
    uint[] bar = groupIdentical(foo);
    // One possibile correct value for bar:
    // bar == [2,2,1,1,3,4,5,5].
    // Another possible correct answer:
    // bar == [1,1,2,2,5,5,4,3].
    

    但是,作为提醒,我们不能假设数据是由整数组成的。

    编辑:谢谢你的回答。哈希的主要问题是哈希表经常执行内存分配。我最终做的是编写自己的哈希表,使用我周围的区域分配器来解决这个问题。效果很好。

9 个答案:

答案 0 :(得分:10)

我认为你可以对对象进行哈希处理,因为实际顺序并不重要,只有分组。相同的对象最终将分组在同一个存储桶中。这假设您感兴趣的每个类型都有自己的哈希函数,或者您可以定义自己的哈希函数并重载它(将每个类型作为参数传递给不同的hashCode函数定义)。

为避免数据类型之间的冲突(因此,对于一个示例,字符串不会在同一个存储桶中与双精度数一起结束),您需要将数据类型编码为散列。因此,例如,如果你有一个32位散列,也许前5位可以编码数据类型,所以你可以在同一个散列映射中有32种不同的类型。

编辑:我想补充一点,我建议使用自定义哈希映射的原因是因为我不知道有哪一个暴露了足够的内部实现来从中获取每个桶的值。可能有这样的实现,我不知道。有很多我不知道的事情。 :)

答案 1 :(得分:4)

您在这里寻找的神奇词汇是 multiset (或 bag )。它根本不是一种排序,因为只要您将所有具有相同键的元素组合在一起,您就不关心该顺序。根据您使用的语言,有几种固定的实现可用,但通常上面的散列版本是渐近最优的,我相信:insert()是常量时间,因为您可以在 O中计算散列(1)并将碰撞插入附加到 O(1)时间的列表中;你可以在 O(1)时间从箱子中检索一个元素,你只需抓住箱子里的第一个元素;因此,您可以在 O(n)时间内收集所有这些内容,因为您为每个元素检索 n O(1)元素。

答案 2 :(得分:3)

一个疾驰的mergesort,比如python的内置排序(cf timsort),当有大量已经排序的数据(例如,在你的例子中,相同的对象)时,它具有良好的预期性能 - 你每次合并都会跳过O(log(N))工作。如果数据集非常大(这称为“外部”排序),您还可以在多个CPU和磁盘上分发合并存储。但是,最糟糕的情况是O(Nlog(N))。

唯一比Nlog(N)更快的排序是计算排序,这些排序利用了密钥的一些公共属性。要使用线性时间排序(散列表或基数/桶排序),您必须对结构进行散列以生成某种数字键。

基数排序将通过键进行多次传递,因此其预期时间将比哈希表方法更长;而且,由于你不关心字典顺序,如果你能负担得起哈希键,哈希表解决方案对你来说听起来更好。

答案 3 :(得分:1)

当有大量重复项时,

3-way QuickSort表现得非常好。

答案 4 :(得分:1)

我认为散列到存储桶中是最好的解决方案,假设存在一个保留operator = mapping的散列(0.0可能不会散列到相同的东西-0.0,但它们可能“相等”)。假设你只有一个等于和小于运算符,你可以实现一个基本的快速排序算法,选择第一个元素作为枢轴,并将少于一个组,并且大于另一个组,然后重复每个小组的过程。

答案 5 :(得分:0)

如果你知道可能值的范围,并且它很小,你可以这样做:(伪代码)

uint[] bucket = new int[10];
foreach(uint val in foo) {
    ++bucket[val];
}

uint bar_i = 0;
uint[] bar = new int[foo.length];
foreach(int val = 0; val < 10; val++) {
    uint occurrences = bucket[val];
    for(int i=0; i < occurrences; i++) {
        bar[bar_i++] = val;
    }
}

答案 6 :(得分:0)

我认为既然你有任意对象你不想复制太多,你可以只使用引用或指针进行排序,如果需要,然后按顺序复制对象。

答案 7 :(得分:0)

也许是R + B或AVL树?然后再说 - 它最终仍然是O(NlogN)。不妨使用heapsort - 不会更糟,没有额外的内存使用...

答案 8 :(得分:0)

性能顺序为O(n(n-1)/ 2)的简单算法如下:

  1. 假设输入数组名为Input,其大小为n。
  2. 为返回数组分配一个内存,其大小相同,命名为Result
  3. 为布尔数组分配一个内存,其大小相同,名为Visited,并将所有Visted设置为false
  4. 假设有一个名为Equals的Equal函数,如果两个项都相等则返回true,否则返回false。
  5. 假设数组索引从1开始到n
  6. 请参阅下面的伪C代码:
function groupIdentical(Input) 
{
    k=1;
    for i=1 to n 
    {
        Visited[i]=false ;
    }

    for i=1 to n
    {
        if( !Visited(i) )
        {   
            Result[k++]=Input[i];
            for j= (i+1) to n
            {
                if( Equals(i,j) )
                {
                    Result[k++]=Input[j];
                    Visited[j]=true;
                }   
            }
        }
    }
    return Result;
}