删除辅助内存最少的重复项目?

时间:2008-12-11 00:45:47

标签: performance algorithm optimization sorting hash

在axillary memory usage必须达到最小值的约束下,从数组中删除重复项的最有效方法是什么,最好小到甚至不需要任何堆分配?排序似乎是明显的选择,但这显然不是渐近有效的。有没有更好的算法可以在适当的位置或接近到位?如果排序是最好的选择,那么什么样的排序最适合这样的事情呢?

5 个答案:

答案 0 :(得分:2)

我会回答我自己的问题,因为在发布之后,我想出了一个非常聪明的算法来做到这一点。它使用散列,构建类似哈希集的东西。它保证在腋窝空间中是O(1)(递归是尾调用),并且通常是O(N)时间复杂度。算法如下:

  1. 获取数组的第一个元素,这将是 sentinel
  2. 尽可能重新排序数组的其余部分,以使每个元素位于与其散列对应的位置。完成此步骤后,将发现重复项。将它们设置为相同的哨兵。
  3. 将索引等于散列的所有元素移动到数组的开头。
  4. 将除了数组的第一个元素之外的所有等于sentinel的元素移动到数组的末尾。
  5. 正确散列元素和重复元素之间的剩余部分将是因碰撞而无法放置在与其散列对应的索引中的元素。递归处理这些要素。
  6. 这可以显示为O(N)在散列中没有提供病理场景: 即使没有重复,在每次递归时也会消除大约2/3的元素。每个递归级别为O(n),其中小n是剩余元素的数量。唯一的问题是,在实践中,当重复很少时,即快速排序,即大量碰撞时,它的速度要慢。然而,当存在大量重复时,它的速度非常快。

    编辑:在D的当前实现中,hash_t是32位。关于此算法的所有内容都假定在完整的32位空间中将存在非常少的哈希冲突(如果有的话)。然而,碰撞可能在模数空间中频繁发生。然而,对于任何合理大小的数据集,这种假设很可能都是正确的。如果密钥小于或等于32位,则它可以是它自己的散列,这意味着完全32位空间中的冲突是不可能的。如果它更大,你根本无法将它们放入32位内存地址空间,因为它是一个问题。我假设在D的64位实现中hash_t将增加到64位,其中数据集可以更大。此外,如果这确实是一个问题,可以在每个递归级别更改散列函数。

    以下是D编程语言的实现:

    void uniqueInPlace(T)(ref T[] dataIn) {
        uniqueInPlaceImpl(dataIn, 0);
    }
    
    void uniqueInPlaceImpl(T)(ref T[] dataIn, size_t start) {
        if(dataIn.length - start < 2)
            return;
    
        invariant T sentinel = dataIn[start];
        T[] data = dataIn[start + 1..$];
    
        static hash_t getHash(T elem) {
            static if(is(T == uint) || is(T == int)) {
                return cast(hash_t) elem;
            } else static if(__traits(compiles, elem.toHash)) {
                return elem.toHash;
            } else {
                static auto ti = typeid(typeof(elem));
                return ti.getHash(&elem);
            }
        }
    
        for(size_t index = 0; index < data.length;) {
            if(data[index] == sentinel) {
                index++;
                continue;
            }
    
            auto hash = getHash(data[index]) % data.length;
            if(index == hash) {
                index++;
                continue;
            }
    
            if(data[index] == data[hash]) {
                data[index] = sentinel;
                index++;
                continue;
            }
    
            if(data[hash] == sentinel) {
                swap(data[hash], data[index]);
                index++;
                continue;
            }
    
            auto hashHash = getHash(data[hash]) % data.length;
            if(hashHash != hash) {
                swap(data[index], data[hash]);
                if(hash < index)
                    index++;
            } else {
                index++;
            }
        }
    
    
        size_t swapPos = 0;
        foreach(i; 0..data.length) {
            if(data[i] != sentinel && i == getHash(data[i]) % data.length) {
                swap(data[i], data[swapPos++]);
            }
        }
    
        size_t sentinelPos = data.length;
        for(size_t i = swapPos; i < sentinelPos;) {
            if(data[i] == sentinel) {
                swap(data[i], data[--sentinelPos]);
            } else {
                i++;
            }
        }
    
        dataIn = dataIn[0..sentinelPos + start + 1];
        uniqueInPlaceImpl(dataIn, start + swapPos + 1);
    }
    

答案 1 :(得分:1)

将辅助内存使用量保持在最低限度,最好的办法是进行有效排序以使它们按顺序排列,然后使用FROM和TO索引对数组进行单次传递。

每次循环都会使FROM索引前进。当密钥与最后一个密钥不同时,您只能将元素从FROM复制到TO(并递增TO)。

使用Quicksort,最终传递的平均值为O(n-log-n)和O(n)。

答案 2 :(得分:0)

如果对数组进行排序,则仍需要另一个传递来删除重复项,因此在最坏的情况下(假设为Quicksort)或O(N sqrt()的复杂度为O(N N) N))使用Shellsort。

你可以通过简单地扫描每个元素的数组来实现O(N * N)去除重复项。

这是Lua中的一个例子:

function removedups (t)
    local result = {}
    local count = 0
    local found
    for i,v in ipairs(t) do
        found = false
        if count > 0 then
            for j = 1,count do
                if v == result[j] then found = true; break end
            end
        end
        if not found then 
            count = count + 1
            result[count] = v 
        end
    end
    return result, count
end

答案 3 :(得分:0)

如果没有像bubblesort这样的东西,我没有看到任何办法。当你找到一个傻瓜时,你需要减少阵列的长度。 Quicksort不是为要改变的数组大小而设计的。

此算法始终为O(n ^ 2),但它也几乎不使用额外的内存 - 堆栈或堆。

// returns the new size
int bubblesqueeze(int* a, int size) {
   for (int j = 0; j < size - 1; ++j) {
      for (int i = j + 1; i < size; ++i) {
          // when a dupe is found, move the end value to index j
          // and shrink the size of the array
          while (i < size && a[i] == a[j]) {
             a[i] = a[--size];
          }
          if (i < size && a[i] < a[j]) {
             int tmp = a[j];
             a[j] = a[i];
             a[i] = tmp;
          }
      }
   }
   return size;
}

答案 4 :(得分:0)

您是否有两个不同的var用于遍历仅有一个数据输入的数据输入,那么您可以通过关闭当前已存在于数据集中的所有重复数据来限制输出。

很明显,C中的这个例子不是一个有效的排序算法,但它只是一个看待问题的方法的例子。

你也可以先盲目排序数据,然后重新定位数据以删除重复数据,但我不确定这会更快。

#define ARRAY_LENGTH 15
int stop = 1;
int scan_sort[ARRAY_LENGTH] = {5,2,3,5,1,2,5,4,3,5,4,8,6,4,1};

void step_relocate(char tmp,char s,int *dataset)
{
    for(;tmp<s;s--)
        dataset[s] = dataset[s-1];
}
int exists(int var,int *dataset)
{
    int tmp=0;
    for(;tmp < stop; tmp++)
    {
        if( dataset[tmp] == var)
            return 1;/* value exsist */
        if( dataset[tmp] > var)
            tmp=stop;/* Value not in array*/
    }
    return 0;/* Value not in array*/
}
void main(void)
{
    int tmp1=0;
    int tmp2=0;
    int index = 1;
    while(index < ARRAY_LENGTH)
    {
        if(exists(scan_sort[index],scan_sort))
            ;/* Dismiss all values currently in the final dataset */
        else if(scan_sort[stop-1] < scan_sort[index])
        {
            scan_sort[stop] = scan_sort[index];/* Insert the value as the highest one */
            stop++;/* One more value adde to the final dataset */
        }
        else
        {
            for(tmp1=0;tmp1<stop;tmp1++)/* find where the data shall be inserted */
            {
                if(scan_sort[index] < scan_sort[tmp1])
                {
                    index = index;
                    break;
                }
            }
            tmp2 = scan_sort[index]; /* Store in case this value is the next after stop*/
            step_relocate(tmp1,stop,scan_sort);/* Relocated data already in the dataset*/
            scan_sort[tmp1] = tmp2;/* insert the new value */
            stop++;/* One more value adde to the final dataset */
        }
        index++;
    }
    printf("Result: ");
    for(tmp1 = 0; tmp1 < stop; tmp1++)
        printf( "%d ",scan_sort[tmp1]);
    printf("\n");
    system( "pause" );
}

我喜欢这个问题,所以我为它编写了一个简单的C测试程序,如上所示。如果我要详细说明或你发现任何错误,请发表评论。