用于从数据流中选择前k(百分比)项的有效算法:

时间:2013-11-18 00:40:14

标签: c algorithm sorting optimization

我必须重复排序一个包含300个随机元素的数组。但我必须做一种特殊的排序:我需要从数组的子集中获得5%的最小值,然后计算一些值并增加子集。现在再次计算值,子集也增加。依此类推,直到子集包含整个数组。

子集以前10个元素开始,每个步骤后增加10个元素。 即:

子集大小k = ceil(5%*子集)
 10 1(所以只是最小的元素)
20 1(所以也只是最小的)
30 2(最小和第二小)

...

计算值基本上是小于k的所有元素和特殊加权的k最小元素之和。 在代码中:

k = ceil(0.05 * subset) -1; // -1 because array index starts with 0...  
temp = 0.0; 
for( int i=0  i<k; i++)
    temp += smallestElements[i];
temp += b *  smallestElements[i];

我已经实现了一个基于选择排序的算法(本文末尾的代码)。我使用MAX(k)指针来跟踪k个最小元素。因此,我不必要地排序小于k的所有元素:/ 此外,我知道选择排序不利于性能,不幸的是,这在我的情况下是至关重要的。

我试图弄清楚如何使用一些基于快速或基于堆的算法。我知道如果k和子集是固定的,quickselect或heapselect对于找到k个最小元素是完美的。 但由于我的子集更像是输入数据流,我认为基于快速排序的算法会丢失。 我知道如果k被修复,heapselect对数据流来说是完美的。但我没有管理它来调整动态k的heapselect而没有大的性能下降,所以它不如我的基于选择排序的版本有效:(任何人都可以帮我修改动态k的堆选择吗?

如果没有更好的算法,您可能会为我的选择排序实现找到一种不同/更快的方法。这是我的实现的最小示例,在此示例中未使用计算变量,因此不必担心它。 (在我的真实程序中,我只是手动展开一些循环以获得更好的性能)

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define ARRAY_SIZE 300
#define STEP_SIZE 10

float sortStream( float*  array, float**  pointerToSmallest, int k_max){    
    int  i,j,k,last = k_max-1;
    float temp=0.0;

// init first two pointers  
    if( array[0] < array[1] ){  
        pointerToSmallest[0] = &array[0];
        pointerToSmallest[1] = &array[1];
    }else{
        pointerToSmallest[0] = &array[1];
        pointerToSmallest[1] = &array[0];
    }
// Init remaining pointers until i= k_max
    for(i=2; i< k_max;++i){
        if( *pointerToSmallest[i-1] < array[i] ){   
            pointerToSmallest[i] = &array[i];
        }else{  
            pointerToSmallest[i] = pointerToSmallest[i-1];
            for(j=0; j<i-1 && *pointerToSmallest[i-2-j] > array[i];++j)
                pointerToSmallest[i-1-j] = pointerToSmallest[i-2-j];            
            pointerToSmallest[i-1-j]=&array[i];         
        }   
        if((i+1)%STEP_SIZE==0){
            k = ceil(0.05 * i)-1;       
            for(j=0; j<k; j++)
                temp += *pointerToSmallest[j];
            temp += 2 * (*pointerToSmallest[k]);
        }
    }
// Selection sort remaining elements    
    for( ; i< ARRAY_SIZE; ++i){     
        if( *pointerToSmallest[ last ] > array[i] ) {       
            for(j=0; j != last && *pointerToSmallest[ last-1-j] > array[i];++j)
                pointerToSmallest[last-j] = pointerToSmallest[last-1-j];            
            pointerToSmallest[last-j] = &array[i];      
        }       
        if( (i+1)%STEP_SIZE==0){
            k = ceil(0.05 * i)-1;       
            for(j=0; j<k; j++)
                temp += *pointerToSmallest[j];  
            temp += 2 * (*pointerToSmallest[k]);        
        }
    }   
    return temp;

}

int main(void){
    int     i,k_max = ceil( 0.05 * ARRAY_SIZE );
    float*  array = (float*)malloc ( ARRAY_SIZE * sizeof(float));
    float** pointerToSmallest = (float**)malloc( k_max * sizeof(float*));
    for( i=0; i<ARRAY_SIZE; i++)
            array[i]= rand() / (float)RAND_MAX*100-50;

    // just return a, so that the compiler doens't drop the function call
    float a = sortStream(array,pointerToSmallest, k_max);
    return (int)a;
}

非常感谢

2 个答案:

答案 0 :(得分:1)

通过使用两个堆来存储流中的所有项目,您可以:

  1. 在O(1)
  2. 中找到前p%元素
  3. 在O(log N)
  4. 中更新数据结构(两个堆)

    假设,现在我们有N个元素,k = p%* N,

    1. min heap(LargerPartHeap)用于存储前k个项目
    2. 用于存储其他(N - k)项目的最大堆(SmallerPartHeap)。
    3. SmallerPartHeap中的所有项目都小于或等于LargerPartHeap的最小项目(top item @ LargerPartHeap)。

      1. 查询“什么是最高p%元素?”,只需返回LargerPartHeap
      2. 用于更新“来自流的新元素x”,

        2.a检查新k'=(N + 1)* p%,如果k'= k + 1,则将SmallerPartHeap的顶部移动到LargerPartHeap。 - O(logN)

        2.b如果x大于LargerPartHeap的top元素(min元素),则将x插入LargerPartHeap,并将LargerPartHeap的顶部移动到SmallerPartHeap;否则,将x插入SmallerPartHeap - O(logN)

答案 1 :(得分:0)

我认为堆排序对于这个特定问题来说太复杂了,即使这个或其他优先级队列算法非常适合从流中获取N个最小或最大项目。

第一个通知是约束0.05 * 300 = 15.这是必须随时排序的最大数据量。在每次迭代期间,还添加了10个元素。整个操作就地可以是:

 for (i = 0; i < 30; i++)
 {
      if (i != 1)
         qsort(input + i*10, 10, sizeof(input[0]), cmpfunc);
      else
         qsort(input, 20, sizeof(input[0]), cmpfunc);

      if (i > 1)
        merge_sort15(input, 15, input + i*10, 10, cmpfunc);
 }

当i == 1时,还可以合并排序输入和输入+ 10以生成完全排序的20位数组,因为它的复杂度低于通用sort。这里的“优化”也是最小化算法的基元。

Merge_sort15只会考虑第一个数组的前15个元素和下一个数组的前10个元素。

编辑问题的参数对选择正确的算法会产生相当大的影响;在这里选择“排序10项”作为基本单元将允许并行化问题的一半,即每个10个项目排序30个单独的块 - 这个问题可以通过使用排序网络的固定管道算法有效地解决。对于不同的参数化,这种方法可能不可行。