O(n) timeでnサイズの配列にk個の最小数を出力できるアルゴリズムを作成しようとしていますが、時間の複雑さをnに減らすことはできません。私はこれをします?
これは以前インタビューで行ったことがありますが、これを行う最もエレガントで効率的な方法の1つは
O(n log k).
with space: O(k) (thanks, @Nzbuu)
基本的に、kに制限されたサイズの最大ヒープを使用します。配列内の各アイテムについて、最大(O(1)のみ)よりも小さいかどうかを確認します。ある場合は、それをヒープ(O(log k))に入れて、最大値を削除します。大きい場合は、次の項目に進みます。
もちろん、ヒープはk個のアイテムのソートされたリストを生成しませんが、O(k log k)で簡単に実行できます。
同様に、最大のk個のアイテムを見つけるために同じことを行うことができます。この場合、最小ヒープを使用します。
「選択アルゴリズム」(O(n))を使用してk番目に小さい要素を見つけてから、配列を再度反復して、それより小さい/等しい各要素を返す必要があります。
選択アルゴリズム: http://en.wikipedia.org/wiki/Selection_algorithm
繰り返しがある場合は注意する必要があります。k個以上の要素を返さないようにする必要があります(たとえば、1,2、...、k、kがある場合は可能です) 、k、...)
EDIT:
完全なアルゴリズム、および要求に応じてリストを返す:配列を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の選択アルゴリズムを使用してkを見つけることができます番目 最小数。それは、配列をより小さな数、k番目 数、およびより大きい数。
問題に対する最善の解決策は次のとおりです。クイックソートを使用してピボットを検索し、このk番目の要素が存在しない部分を破棄して、次のピボットを再帰的に検索します。 (これはk番目のMax Finderで、if else条件を変更してk番目のMin Finderにする必要があります)。JavaScript code-
// 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));
O(n)
time(つまり、O(n)
ではなく、真のO(n + some function of k)
time)で、n個の要素のうち最小のk個を見つけることができます。 ウィキペディアの記事「選択アルゴリズム」 、特に「順序なし部分ソート」および「ピボット戦略としての中央値選択」のサブセクション、および も参照してください。このO(n)
を作成する重要な部分については、記事「中央値の中央値」 。
これは、予想される線形時間(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)時間と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アルゴリズムまたはクイック選択。
基本的には、配列に対してQuicksortを実行しますが、ピボットよりもKまたはK-1大きい要素を確保するために必要なパーティションの側でのみ実行します(ピボットを除外するlrを含めることができます)。リストを並べ替える必要がない場合は、ピボットから配列の残りを印刷するだけです。クイックソートは所定の場所で実行できるため、これにはO(n)スペースが必要です。また、配列の一部(平均)が半分になるため、毎回O(2n) == O(n)時間
別のテクニック-QuickSelectアルゴリズムを使用すると、結果は返された結果の左側にあるすべての要素になります。平均時間の複雑さはO(n)であり、最悪の場合はO(n ^ 2)になります。空間の複雑さはO(1)になります。
Merge Sortで配列をソートし、最初のk数を出力するだけで、最悪の場合はn * log2(n)を取ります。
これは、選択アルゴリズムにおける再帰の基本条件のわずかな変化であり、ランダムな順序で最初のk個の最小要素をすべて含む動的配列へのポインタを返します。これはO(n)です。
void swap(int *a, int *b){
int temp = *a;
*a = *b;
*b = temp;
}
int partition(int *A, int left, int right){
int pivot = A[right], i = left, x;
for (x = left; x < right; x++){
if (A[x] < pivot){
swap(&A[i], &A[x]);
i++;
}
}
swap(&A[i], &A[right]);
return i;
}
int* quickselect(int *A, int left, int right, int k){
//p is position of pivot in the partitioned array
int p = partition(A, left, right);
//k equals pivot got lucky
if (p == k-1){
int*temp = malloc((k)*sizeof(int));
for(int i=left;i<=k-1;++i){
temp[i]=A[i];
}
return temp;
}
//k less than pivot
else if (k - 1 < p){
return quickselect(A, left, p - 1, k);
}
//k greater than pivot
else{
return quickselect(A, p + 1, right, k);
}
}
ヒープを使用して値を保存する方法はどうですか。配列内の各値を調べると、このコストはnになります。
次に、ヒープを通過して最小のk値を取得します。
ランタイムはO(n) + O(k) = O(n)
もちろん、メモリ空間はO(n + n)になりました