简述
给定n个数,从中找到最大的k个数
思路
- 思路1:最简单的想法,把给定的n个数排序一遍,再取前k个数,复杂度是O(n*logn) + O(k),这里排序用的是快排,顺手下了一个,代码如下
void quickSort(int* array, int begin, int end)
{
int first = begin;
int last = end;
int key = array[first];
//terminate
if (first >= last)
return;
//swap
while (first < last)
{
while (first < last && array[last] <= key)
last--;
array[first] = array[last];
while (first < last && array[first] >= key)
first++;
array[last] = array[first];
}
array[first] = key;
//recursion
quickSort(array, begin, first - 1);
quickSort(array, last + 1, end);
}
- 思路2:如果k很小,比如k为1时,只需要找到最大的数,那么进行n-1次比较就行了,用思路1的方法显然浪费时间,所以这个给出了一个O(n*k)的算法,在k << logn的时候,这个算法比思路1要快的多。大概思路如下:找到n个数中前k个数,放入一个数组kbig中,并找到kbig中最小的数的下标。然后对n进行遍历,每次遍历比较当前n下标的数和kbig中最小的数哪个大,若n当前下标的那个数大,则把kbig中最小的数赋值为n当前下标的数,然后再对kbig中做一次遍历,找到新的kbig中最小的数。(也可以不用开一个新的数组,直接在n上做交换,这样做的效果就是对前k大的数做了排序,放到前k个位置上)时间复杂度是O(n * k),函数代码如下:
void selectK(int* array, int* kBig, const int numConst, int k)
{
//one condition
if (k >= numConst)
{
for (int i = 0; i < numConst; i++)
{
kBig[i] = array[i];
}
}
//another condition
else
{
int minIndex;
for (int i = 0; i < numConst; i++)
{
if(i < k)
{
kBig[i] = array[i];
continue;
}
if (i == k)
{
minIndex = 0;
for(int j = 1; j < k; j++)
{
if(kBig[j] < kBig[minIndex])
{
minIndex = j;
}
}
}
if (array[i] > kBig[minIndex])
{
kBig[minIndex] = array[i];
minIndex = 0;
for(int j = 1; j < k; j++)
{
if(kBig[j] < kBig[minIndex])
{
minIndex = j;
}
}
}
}
}
}
- 思路3:与思路2相同,但是在找当前kbig中最小数的时候做了优化,通过维护一个最小堆,来加速查找和插入的过程,时间复杂度是O(n*logk),代码如下,由于这里查找的都是正数,所以投篮了,一开始把堆中元素都设为-1:
void kBigHeap(int* array, int* kBig, const int numConst, int k)
{
//one condition
if (k >= numConst)
{
for (int i = 0; i < numConst; i++)
{
kBig[i] = array[i];
}
}
else
{
for (int i = 0; i < k; i++)
kBig[i] = -1;
for (int i = 0; i < numConst; i++)
{
if(array[i] > kBig[0])
{
kBig[0] = array[i];
int p = 0;
int q;
while(p < k)
{
q = 2 * p + 1;
if(q >= k)
break;
if(q < k - 1 && kBig[q + 1] < kBig[q])
q++;
if(kBig[q] < kBig[p])
{
int tmp = kBig[q];
kBig[q] = kBig[p];
kBig[p] = tmp;
p = q;
}
else
break;
}
}
}
}
}
- 思路4:采用分治的办法,把一个复杂的问题分成两个规模小的问题来解决。在n数组中随即找到一个元素X,把数据分成sa和sb,sa中元素大于等于X,sb中元素小于X,这时有两种可能性:(1)sa中元素的个数小于k,那么sa中所有的数和sb中最大的k-|sa|个元素就是数组n中最大的k个数
(2)sa中元素个数大于等于k,则需要返回sa中最大的k个元素
给出核心函数代码如下,用Python写的,时间复杂度也是O(n*logk):
import random
def kBig(s, k):
if k <= 0:
return []
if (len(s) <= k):
return s
sa, sb = partition(s)
ka = kBig(sa, k)
kb = kBig(sb, k - len(sa))
ka.extend(kb)
return ka
def partition(s):
sa = []
sb = []
tmp = len(s) * random.random() * 0.999
stmp = s[0]
s[0] = s[int(tmp)]
s[int(tmp)] = stmp
p = s[0]
for i in s[1:]:
if i > p:
sa.append(i)
else:
sb.append(i)
if len(sa) < len(sb):
sa.append(p)
else:
sb.append(p)
return sa,sb
思路5:如果N个数都是正整数,且有取值范围,可以申请一个空间,如取值范围是1~MAX,可以开一个数组count[MAX],其中count[i]代表i+1出现的次数,遍历一次就可以得到所有整数出现的次数,然后寻找第K大的数(找到第K大的代表比这个数大的都是前K个最大的数,再遍历一次即可)
思路6:如果不是正整数的情况,可以划分区间,如N个数中最大的是Vmax,最小的是Vmin,划分成M块,则每块区域是d = (Vmax - Vmin)/ M,第i块区域为[Vmin + (i - 1) × d, Vmin + i × d],然后用思路5的方法,等找到第K大的数所在的区域,再遍历一次,把那个区间的数都放到一个数组中排序,就能找到,所以M越大,需要空间就越大,但是找数的时候就会减少时间复杂度,但是M越小,找数的时候就比较复杂,如果M = 1,相当于对N个数进行排序,那么就是O(N×logN),需要去一个平衡。