【算法】topK问题:得到序列中前k小的数(含复杂度分析)

思路一

  • 利用排序方法(快排/堆排序)对序列从小到大进行排序

  • 输出序列前k个数

  void TopK( int a[], int n, int k )
  {
    if(k<0 || k>n) return;
    sort(a,n);
    for(int i=0;i<k;++i)
        cout<<a[i]<<" ";
  }

时间复杂性分析

  • 快排/堆排序的时间复杂度为O(nlogn),输出前k个数的时间复杂度为O(k),总的时间复杂度为O(nlogn)。但是,该方法对其余不需要的数也进行了排序,浪费了大量的排序时间。

思路二

  • 类似查找第k小的数,利用快排中的划分Partition()函数,找到第k小的数a[k-1],则a[0...k-1]是前k小的数(无序)

  • 如果需要有序的前k小的数,则对a[0...k-1]进行快速排序。

 int Partition(int a[],int left,int right)
 {
    if(left>right) return;
    
    int pivot = a[left];
    int l = left,r = right;
    int t;
    while(l<r)
    {
        while(a[r]>=pivot && l<r) r--;
        while(a[l]<=pivot && l<r) l++;
        if(l<r){ t=a[r];a[r]=a[l];a[l]=t; }
    }
    a[left] = a[l];
    a[l] = pivot;
    return l;
 }
​
 void Select(int a[],int left,int right,int k)
 {
    if(left==right) return;
    else
    {
        int middle = Partition(a,left,right);
        if(middle-left >= k) Select(a,left,middle,k);
        else Select(a,middle+1,right,k-(middle-left));
    }
 }
​
 void TopK( int a[], int n, int k )
  {
    if(k<0 || k>n) return;
    
     Select(a,0,n,k);
     // sort(a,k); // 如果需要输出的前k个数有序,就对前k个数进行排序(可用快排或堆排序)
     
     for(int i=0;i<k;++i)
        cout<<a[i]<<" ";
  }

时间复杂性分析

  • 如果是无序的前k个数,复杂性与查找第k小的数复杂性相同,为O(n)

  • 如果是有序的前k个数,复杂性在O(n)的基础上,还要加上对前k个数排序的复杂性O(klogk),复杂性为O(n+klogk)

思路三

  • 先将序列中的元素a[0...k-1]建立成大根堆,再将a[k...n-1]逐个输入,与堆顶元素比较

  • 如果新输入的元素x比堆顶的元素大,则不放入堆中;如果新输入的元素比堆顶的元素小,则将堆顶的元素deQueue(),并将x用enQueue()函数放入堆中

  • 保持堆里元素始终有k个

  • 最终堆中剩余的k个元素即为前k小的数

template <class Type>
class priorityQueue
{
private:
    int currentSize;
    Type *array;
    int maxSize;
​
    void doubleSpace();
    void buildHeap();
    void percolateDown(int hole);
​
public:
    priorityQueue(int capacity=100)
    {
        array = new Type[capacity];
        maxSize = capacity;
        currentSize = 0;
    }
    priorityQueue(const Type data[], int size);
    ~priorityQueue() { delete[]array; }
​
    bool isEmpty() const { return currentSize == 0; }
    void enQueue(const Type&x);
    Type deQueue();
    Type getHead() const { return array[1]; }
    void print() const{ for(int i=1;i<=currentSize;++i) cout<<array[i]<<" ";}
};
​
template <class Type>
void priorityQueue<Type>::enQueue(const Type&x)
{
    if (currentSize == maxSize - 1) doubleSpace();
​
    // 向上过滤
    int hole = ++currentSize;
    for (; hole > 1 && x < array[hole / 2]; hole /= 2)
        array[hole] = array[hole / 2];
    array[hole] = x;
}
​
template <class Type>
Type priorityQueue<Type>::deQueue()
{
    Type minItem;
    minItem = array[1];
    array[1] = array[currentSize --];
    percolateDown(1);
    return minItem;
}
​
template <class Type>
void priorityQueue<Type>::percolateDown(int hole)
{
    int child;
    Type tmp = array[hole];
​
    for (; hole * 2 <= currentSize; hole = child)
    {
        child = hole * 2;
        if (child != currentSize&&array[child + 1] < array[child])
            child++;
        if (array[child] < tmp) array[hole] = array[child];
        else break;
    }
    array[hole] = tmp;
}
​
template<class Type>
void priorityQueue<Type>::buildHeap()
{
    for (int i = currentSize / 2; i > 0; i--)
        percolateDown(i);
}
​
template<class Type>
priorityQueue<Type>::priorityQueue(const Type data[],int size)
    :maxSize(size+10),currentSize(size)
{
    array = new Type[maxSize];
    for (int i = 0; i < size; i++) array[i + 1] = data[i];
    buildHeap();
}
​
template<class Type>
void priorityQueue<Type>::doubleSpace()
{
    Type*tmp = array;
​
    maxSize *= 2;
    array = new Type[maxSize];
​
    for (int i = 0; i <= currentSize; ++i) array[i] = tmp[i];
    delete []tmp;
}
​
void TopK(int a[],int n,int k)
{
    if(k<0 || k>n) return;
    
    priorityQueue<int> heap(a,k);  // 时间复杂性O(k)
    
    for(int i=k;i<n;++i)
    {
        if(a[i]<heap.getHead()){
            heap.deQueue();    // 时间复杂性O(klogk)
            heap.enQueue(a[i]);  // 时间复杂性O(klogk)
        }
    }
    
    // for(int i=0;i<k;++i)  cout << heap.deQueue() << " "; //输出有序的前k小的数
    
    heap.print(); // 输出无序的前k小的数 
}

时间复杂性分析

  • 直接根据a[0...k]建堆,时间复杂性为O(k)。遍历a[0...n-1]的时间复杂性为O(n)。找到比堆顶元素小的数后,进堆的时间复杂性为O(logk),出堆的时间复杂性为O(logk)。输出有序的前k小的数的时间复杂性为O(nlogk),输出无序的前k小的数的时间复杂性为O(k)

  • 综上,时间复杂性为O(nlogk)

思路四(大数据)

  • Hash算法+思路三

  • 大数据一般先hash映射降低数据规模,然后hash统计加载到内存,最后排序

  • 分而治之再归并

  • 假设 数据大小内存大小​ 向下取整,可以hash/m分成m份,把大数据存储到m个小文件中,逐个读小文件,每个小文件中求出前k小的数,再对这m个小文件中前k小的数用思路三中建立堆的思想,求出总的前k小的数

  • 几种常用的Hash函数:

    • 直接取余法:f(x):= x % maxM ; // maxM一般是不太接近 2^t 的一个质数。 

    • 乘法取整法:f(x):=trunc((x/maxX)maxlongit) % maxM;// 主要用于实数。

    • 平方取中法:f(x):=(xx div 1000 ) % 1000000); //平方后取中间的,每位包含信息比较多。

时间复杂性分析

  • 每个文件的排序的时间复杂性为O(n/m+klogk),总共有m个文件,故所有数据的时间复杂性为O(m(n/m+klogk))=O(n+mklogk)。归并的时间复杂性为O(mklogk)。总的时间复杂性为O(n+mklogk)

  • 以上为理想情况,实际上,主要的时间花费在内外存之间的数据读写上

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iteapoy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值