思路一
-
利用排序方法(快排/堆排序)对序列从小到大进行排序
-
输出序列前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)
-
以上为理想情况,实际上,主要的时间花费在内外存之间的数据读写上