我正在尝试编写一种算法,该算法可以在O(n)时间内打印n个大小数组中的k个最小数字,但我无法将时间复杂度降低到n。我怎样才能做到这一点?
我之前在接受采访时已经这样做了,其中一种最优雅/最有效的方法就是这样做
O(n log k).
with space: O(k) (thanks, @Nzbuu)
基本上你将使用大小限制为k的最大堆。对于数组中的每个项目,检查它是否小于最大值(仅O(1))。如果是,则将其放入堆中(O(log k))并删除最大值。如果它更大,请转到下一个项目。
当然,堆不会产生k个项的排序列表,但这可以在O(k log k)中完成,这很容易。
类似地,您可以执行相同的操作来查找最大的k项,在这种情况下,您将使用最小堆。
只需使用Merge Sort对数组进行排序,然后打印第一个k数,在最坏的情况下将采用n * log2(n)。
如何使用堆来存储值。当您遍历数组中的每个值时,此成本为n。
然后通过堆来获得最小的k值。
运行时间为O(n)+ O(k)= O(n)
当然,内存空间现在是O(n + n)
如上所述,有两种方法可以完成这样的任务:
1)您可以使用n
,quicksort或任何所需的heapsort排序算法对整个O (n log n)
元素数组进行排序,然后在数组中选择m
最小值。这种方法适用于O(n log n)
。
2)您可以使用selection algorithm来填充数组中的m
最小元素。它需要O(n)
时间来找到第k个最小值,因为你将迭代这个算法m次,总时间将是m x O(n) = O(n)
。
你需要使用'选择算法'找到第k个最小元素,即O(n),然后再次迭代数组并返回每个小于/等于它的元素。
选择算法:http://en.wikipedia.org/wiki/Selection_algorithm
如果你有重复,你将不得不注意:你需要确保你没有返回更多的k元素(例如你有1,2,...,k,k,k,... 。)
编辑:
完整的算法,并按要求返回一个列表:让数组为A
1. find the k'th element in A using 'selection algorithm', let it be 'z'
2. initialize an empty list 'L'
3. initialize counter<-0
4. for each element in A:
4.1. if element < z:
4.1.1. counter<-counter + 1 ; L.add(element)
5. for each element in A:
5.1. if element == z AND count < k:
5.1.1. counter<-counter + 1 ; L.add(element)
6. return L
请注意,如果列表可能有重复项,则需要进行第3次迭代。如果它不能 - 它是不必要的,只需将4.1中的条件更改为<=。 还要注意:L.add正在向链表中插入一个元素,因此是O(1)。
假设您正在尝试显示K个最小数字,您可以使用Hoare的Select算法来查找第k个最小数字。将数组划分为较小的数字,第k个数字和较大的数字。
这可以在预期的线性时间(O(n))内完成。首先找到数组的kth
最小元素(使用数据分区方法查找kth
顺序统计量),然后简单地遍历循环以检查哪些元素小于kth
最小元素。请注意,这仅适用于不同的元素。
这是c中的代码:
/*find the k smallest elements of an array in O(n) time. Using the Kth order
statistic-random pivoting algorithm to find the kth smallest element and then looping
through the array to find the elements smaller than kth smallest element.Assuming
distinct elements*/
#include <stdio.h>
#include <math.h>
#include <time.h>
#define SIZE 10
#define swap(X,Y) {int temp=X; X=Y; Y=temp;}
int partition(int array[], int start, int end)
{
if(start==end)
return start;
if(start>end)
return -1;
int pos=end+1,j;
for(j=start+1;j<=end;j++)
{
if(array[j]<=array[start] && pos!=end+1)
{
swap(array[j],array[pos]);
pos++;
}
else if(pos==end+1 && array[j]>array[start])
pos=j;
}
pos--;
swap(array[start], array[pos]);
return pos;
}
int order_statistic(int array[], int start, int end, int k)
{
if(start>end || (end-start+1)<k)
return -1; //return -1
int pivot=rand()%(end-start+1)+start, position, p;
swap(array[pivot], array[start]);
position=partition(array, start, end);
p=position;
position=position-start+1; //size of left partition
if(k==position)
return array[p];
else if(k<position)
return order_statistic(array, start,p-1,k);
else
return order_statistic(array,p+1,end,k-position);
}
void main()
{
srand((unsigned int)time(NULL));
int i, array[SIZE],k;
printf("Printing the array...\n");
for(i=0;i<SIZE;i++)
array[i]=abs(rand()%100), printf("%d ",array[i]);
printf("\n\nk=");
scanf("%d",&k);
int k_small=order_statistic(array,0,SIZE-1,k);
printf("\n\n");
if(k_small==-1)
{
printf("Not possible\n");
return ;
}
printf("\nk smallest elements...\n");
for(i=0;i<SIZE;i++)
{
if(array[i]<=k_small)
printf("%d ",array[i]);
}
}
有可能在O(n)
时间找到k个最小的n个元素(我的意思是真正的O(n)
时间,而不是O(n + some function of k)
)。请参阅the Wikipedia article "Selection algorithm",特别是关于“无序部分排序”和“中间选择作为枢轴策略”的小节,以及the article "Median of medians"作为制作此O(n)
的基本部分。
该问题的最佳解决方案如下:使用快速排序来查找枢轴并丢弃此第k个元素不存在的部分,并递归地找到下一个枢轴。 (这是第k个最大查找器,你需要改变if else条件才能使它成为最小的查找器。)这是JavaScript代码 -
// Complexity is O(n log(n))
var source = [9, 2, 7, 11, 1, 3, 14, 22];
var kthMax = function(minInd, MaxInd, kth) {
// pivotInd stores the pivot position
// for current iteration
var temp, pivotInd = minInd;
if (minInd >= MaxInd) {
return source[pivotInd];
}
for (var i = minInd; i < MaxInd; i++) {
//If an element is greater than chosen pivot (i.e. last element)
//Swap it with pivotPointer element. then increase ponter
if (source[i] > source[MaxInd]) {
temp = source[i];
source[i] = source[pivotInd];
source[pivotInd] = temp;
pivotInd++;
}
}
// we have found position for pivot elem.
// swap it to that position place .
temp = source[pivotInd];
source[pivotInd] = source[MaxInd];
source[MaxInd] = temp;
// Only try to sort the part in which kth index lies.
if (kth > pivotInd) {
return kthMax(pivotInd + 1, MaxInd, kth);
} else if (kth < pivotInd) {
return kthMax(minInd, pivotInd - 1, kth);
} else {
return source[pivotInd];
}
}
// last argument is kth-1 , so if give 2 it will give you,
// 3rd max which is 11
console.log(kthMax(0, source.length - 1, 2));
另一种技术 - 使用QuickSelect算法,结果将是返回结果左侧的所有元素。平均时间复杂度为O(n),在最坏的情况下,它将为O(n ^ 2)。空间复杂度为O(1)。
我不知道你在寻找什么,但非常简单的O(n * k)时间和O(k)空间。这是最大的K因此需要翻转它。
对于最小的k(结果)的粗暴可以替换堆
private int[] FindKBiggestNumbersM(int[] testArray, int k)
{
int[] result = new int[k];
int indexMin = 0;
result[indexMin] = testArray[0];
int min = result[indexMin];
for (int i = 1; i < testArray.Length; i++)
{
if(i < k)
{
result[i] = testArray[i];
if (result[i] < min)
{
min = result[i];
indexMin = i;
}
}
else if (testArray[i] > min)
{
result[indexMin] = testArray[i];
min = result[indexMin];
for (int r = 0; r < k; r++)
{
if (result[r] < min)
{
min = result[r];
indexMin = r;
}
}
}
}
return result;
}
这可以使用O(n)空间在O(n)时间内完成,我相信。如前所述,您可以使用Hoares算法或quickselect的变体。
基本上你在阵列上运行Quicksort,但只在分区一侧运行,以确保有比K轴更大的K或K-1元素(你可以包括lr排除枢轴)。如果列表不需要排序,则可以从数据透视表中打印数组的剩余部分。由于快速排序可以在适当的位置完成,这需要O(n)空间,并且因为你是数组的一半(平均),你每次检查需要O(2n)== O(n)时间