web-dev-qa-db-ja.com

配列内の上位N個の要素を見つける

順不同リスト(たとえば100)で上位N(たとえば10)個の要素を見つけるための最良のソリューションは何でしょうか。

私の頭に浮かんだ解決策は、1。クイックソートを使用してソートし、2。トップ10を取得することでした。

しかし、より良い代替手段はありますか?

29
zengr

時間は線形時間に短縮できます。

  1. 選択アルゴリズム を使用します。これにより、並べ替えられていない配列のk番目の要素が線形時間で効率的に検索されます。クイックソートのバリアントまたはより堅牢なアルゴリズムを使用できます。

  2. 手順1で取得したピボットを使用して、上位kを取得します。

24
Yin Zhu

すべてをJava;)に委任するのはどうですか

function findTopN(Array list, int n)
{
    Set sortedSet<Integer> = new TreeSet<>(Comparators.naturalOrder());

    // add all elements from list to sortedSet

    // return the first n from sortedSet
}

これが最善の方法だと言っているのではありません。 Yin Zhuがk番目に大きい要素を見つける方法が最良の答えだとまだ思っています。

9
nuaavee

固定長整数などの単純な要素を扱う場合、入力データと同じサイズのメモリバッファを確保できるなら、O(n) timeバケットまたは基数ソートを使用すると、これが最速になります。

線形時間選択アルゴリズムがありますが、隠された定数は非常に高い-24前後。つまり、O(nlog n)アルゴリズムは通常、数百万未満の要素に対して高速になります。

それ以外の場合、2つの要素のみを比較してどちらが大きいかを判断できる一般的な場合、問題は ヒープデータ構造 で解決するのが最適です。

N個のアイテムの上位k個が必要だとします。データの完全な並べ替えに基づくすべてのソリューションはO(nlog n)時間を必要としますが、ヒープを使用するのに必要な時間はO(nlog k)だけです。最初のk要素にヒープを構築し、その後要素を追加して最大値を削除します。これにより、最小のk要素を含むヒープが残ります。

8
j_random_hacker

はい、トップNの(ソートされた)実行リストを保持するだけで、O(n)で実行できます。通常のライブラリ関数または sorting network 。たとえば、3を使用し、実行リストのどの要素が各反復を変更するかを示す簡単なデモ。

5 2 8 7 9

i = 0
top[0] <= 5

i = 1
top[1] <= 2

i = 2
top[2] <= top[1] (2)
top[1] <= top[0] (5)
top[0] <= 8

i = 3
top[2] <= top[1] (5)
top[1] <= 7

i = 4
top[2] <= top[1] (7)
top[1] <= top[0] (8)
top[0] <= 9
4

最適な解決策は、選択した言語が提供するあらゆる機能を使用して、生活を楽にすることです。

ただし、これがどのアルゴリズムを選択する必要があるかに関連する質問であると仮定して、ここで別のアプローチを提案します。 100から10程度の話をしている場合、パフォーマンスを気にする必要はありません多く 1秒あたりの回数。

たとえば、このCコード(これはばかげたことをせずにできるほど非効率的です)の実行には、まだ10分の1秒もかかりません。コーヒーを飲みに行くことを考えるのに十分な時間ではありません。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define SRCSZ 100
#define DSTSZ 10

int main (void) {
    int unused[SRCSZ], source[SRCSZ], dest[DSTSZ], i, j, pos;

    srand (time (NULL));
    for (i = 0; i < SRCSZ; i++) {
        unused[i] = 1;
        source[i] = Rand() % 1000;
    }

    for (i = 0; i < DSTSZ; i++) {
        pos = -1;
        for (j = 0; j < SRCSZ; j++) {
            if (pos == -1) {
                if (unused[j]) {
                    pos = j;
                }
            } else {
                if (unused[j] && (source[j] > source[pos])) {
                    pos = j;
                }
            }
        }
        dest[i] = source[pos];
        unused[pos] = 0;
    }

    printf ("Source:");
    for (i = 0; i < SRCSZ; i++) printf (" %d", source[i]);
    printf ("\nDest:");
    for (i = 0; i < DSTSZ; i++) printf (" %d", dest[i]);
    printf ("\n");

    return 0;
}

timeを介して実行すると、出力が得られるように出力を少しフォーマットしましたが、結果には影響しません):

Source: 403 459 646 467 120 346 430 247 68 312 701 304 707 443
        753 433 986 921 513 634 861 741 482 794 679 409 145 93
        512 947 19 9 385 208 795 742 851 638 924 637 638 141
        382 89 998 713 210 732 784 67 273 628 187 902 42 25
        747 471 686 504 255 74 638 610 227 892 156 86 48 133
        63 234 639 899 815 986 750 177 413 581 899 494 292 359
        60 106 944 926 257 370 310 726 393 800 986 827 856 835
        66 183 901
Dest: 998 986 986 986 947 944 926 924 921 902

real    0m0.063s
user    0m0.046s
sys     0m0.031s

心配する必要があるのは、数値の量が大きくなった場合のみです。誤解しないでください。パフォーマンスについて思考してはいけないと言っているのではありません。やってはいけないことは、重要ではないもの-YAGNIとすべてのジャズ-を最適化するのに時間をかけすぎることです。

すべての最適化の質問と同様に、測定は推測しないでください!

3
paxdiablo

Listを使用し、グアバのComparatorsクラスを使用して、目的の結果を得ることができます。これは高度に最適化されたソリューションです。以下のサンプルをご覧ください。上位5つの数値が取得されます。 APIは こちら にあります。

import Java.util.Comparator;
import Java.util.List;
import Java.util.stream.Collector;

import org.junit.Test;

import com.google.common.collect.Comparators;
import com.google.common.collect.Lists;

public class TestComparator {

    @Test
    public void testTopN() {
        final List<Integer> numbers = Lists.newArrayList(1, 3, 8, 2, 6, 4, 7, 5, 9, 0);
        final Collector<Integer, ?, List<Integer>> collector = Comparators.greatest(5,
                Comparator.<Integer>naturalOrder());
        final List<Integer> top = numbers.stream().collect(collector);
        System.out.println(top);
    }

}

出力:[9、8、7、6、5]

2
Pritesh Mhatre

さて、O(n) timeでソートされていない配列からヒープを作成でき、O(log(n)) time。したがって、合計ランタイムはO(n + k * log(n))です。

1
Charles Munger

選択ソートと挿入ソートの両方の実装の下に書かれています。より大きなデータセットの場合、選択ソートよりも挿入ソートの方が良いことをお勧めします

public interface FindTopValues
{
  int[] findTopNValues(int[] data, int n);
}

挿入ソートの実装:

public class FindTopValuesInsertionSortImpl implements FindTopValues {  

/**
 * Finds list of the highest 'n' values in the source list, ordered naturally, 
 * with the highest value at the start of the array and returns it 
 */
@Override
public int[] findTopNValues(int[] values, int n) {

    int length = values.length;
    for (int i=1; i<length; i++) {
        int curPos = i;
        while ((curPos > 0) && (values[i] > values[curPos-1])) {
            curPos--;
        }

        if (curPos != i) {
            int element = values[i];
            System.arraycopy(values, curPos, values, curPos+1, (i-curPos));
            values[curPos] = element;
        }
    }       

    return Arrays.copyOf(values, n);        
}   

}

選択ソートの実装:

public class FindTopValuesSelectionSortImpl implements FindTopValues {

/**
 * Finds list of the highest 'n' values in the source list, ordered naturally, 
 * with the highest value at the start of the array and returns it 
 */
@Override
public int[] findTopNValues(int[] values, int n) {
    int length = values.length;

    for (int i=0; i<=n; i++) {
        int maxPos = i;
        for (int j=i+1; j<length; j++) {
            if (values[j] > values[maxPos]) {
                maxPos = j;
            }
        }

        if (maxPos != i) {
            int maxValue = values[maxPos];
            values[maxPos] = values[i];
            values[i] = maxValue;
        }           
    }
    return Arrays.copyOf(values, n);        
}
}
1

インタビューで同じアルゴリズムを求められました。誰かがそれをJava-の最速のアルゴリズムと比較することができれば非常に便利です。

    public int[] findTopNValues(int[] anyOldOrderValues, int n) {
        if (n < 0) {
            return new int[]{};
        }
        if (n == 1) {
            return new int[]{findMaxValue(anyOldOrderValues)};
        }

        int[] result = new int[n + 1];
        for (int i = 0; i < Math.min(n, anyOldOrderValues.length); i++) {
            result[i] = anyOldOrderValues[i];
        }
        Arrays.sort(result);

        int max = result[0];
        for (int i = n - 1; i < anyOldOrderValues.length; i++) {
            int value = anyOldOrderValues[i];
            if (max < value) {
                result[n] = value;
                Arrays.sort(result);
                int[] result1 = new int[n + 1];
                System.arraycopy(result, 1, result1, 0, n);
                result = result1;
                max = result[0];
            }
        }
        return convertAndFlip(result, n);
    }

    public static int[] convertAndFlip(int[] integers, int n) {
        int[] result = new int[n];
        int j = 0;
        for (int i = n - 1; i > -1; i--) {
            result[j++] = integers[i];
        }
        return result;
    }

そしてそのためのテスト:

public void testFindTopNValues() throws Exception {
    final int N = 100000000;
    final int MAX_VALUE = 100000000;
    final int returnArray = 1000;
    final int repeatTimes = 5;

    FindTopValuesArraySorting arraySorting = new FindTopValuesArraySorting();

    int[] randomArray = createRandomArray(N, MAX_VALUE);
    for (int i = 0; i < repeatTimes; i++) {

        long start = System.currentTimeMillis();
        int[] topNValues = arraySorting.findTopNValues(randomArray, returnArray);
        long stop = System.currentTimeMillis();

        System.out.println("findTopNValues() from " + N + " elements, where MAX value=" + (MAX_VALUE - 1) + " and return array size " + returnArray + " elements : " + (stop - start) + "msec");
        // System.out.println("Result list = " + Arrays.toString(topNValues));
    }
}

private static int[] createRandomArray(int n, int maxValue) {
    Random r = new Random();
    int[] arr = new int[n];
    for (int i = 0; i < n; i++) {
        arr[i] = r.nextInt(maxValue);
    }
    return arr;
}

結果は次のようになります。

findTopNValues() from 100000000 elements, where MAX value=99999999 and return array size 1000 elements : 395msec
findTopNValues() from 100000000 elements, where MAX value=99999999 and return array size 1000 elements : 311msec
findTopNValues() from 100000000 elements, where MAX value=99999999 and return array size 1000 elements : 473msec
findTopNValues() from 100000000 elements, where MAX value=99999999 and return array size 1000 elements : 380msec
findTopNValues() from 100000000 elements, where MAX value=99999999 and return array size 1000 elements : 406msec

100.000.000個の初期要素の配列から最大1000個の整数を取得した場合の平均結果は約400mscです。悪くない!

上記からそのセットを試してみました:

findTopNValues() from 101 elements and return array size 10 elements : 1msec
Result list = [998, 986, 986, 986, 947, 944, 926, 924, 921, 902]
Original list = [403, 459, 646, 467, 120, 346, 430, 247, 68, 312, 701, 304, 707, 443, 753, 433, 986, 921, 513, 634, 861, 741, 482, 794, 679, 409, 145, 93, 512, 947, 19, 9, 385, 208, 795, 742, 851, 638, 924, 637, 638, 141, 382, 89, 998, 713, 210, 732, 784, 67, 273, 628, 187, 902, 42, 25, 747, 471, 686, 504, 255, 74, 638, 610, 227, 892, 156, 86, 48, 133, 63, 234, 639, 899, 815, 986, 750, 177, 413, 581, 899, 494, 292, 359, 60, 106, 944, 926, 257, 370, 310, 726, 393, 800, 986, 827, 856, 835, 66, 183, 901]
0
Dmitri Algazin

はい、クイックソートよりも優れた方法があります。 Yin Zhuが指摘したように、最初にk番目に大きい要素を検索し、その要素の値をピボットとして使用して配列を分割できます。

0
CodeKata

最適なアルゴリズムは、Kのサイズに大きく依存します。Kが小さい場合は、BubbleSortアルゴリズムに従って、外側のループをK回繰り返すだけで上位Kの値が得られます。複雑さはO(n * k)になります。

ただし、Kの値がnに近い場合、複雑度はO(n ^ 2)に近づきます。このようなシナリオでは、クイックソートが適切な代替手段になる可能性があります。

0
user3734336
public class FindTopValuesSelectionSortImpl implements FindTopValues {

/**
 * Finds list of the highest 'n' values in the source list, ordered naturally, 
 * with the highest value at the start of the array and returns it 
 */
@Override
public int[] findTopNValues(int[] values, int n) {
    int length = values.length;

    for (int i=0; i<=n; i++) {
        int maxPos = i;
        for (int j=i+1; j<length; j++) {
            if (values[j] > values[maxPos]) {
                maxPos = j;
            }
        }

        if (maxPos != i) {
            int maxValue = values[maxPos];
            values[maxPos] = values[i];**strong text**
            values[i] = maxValue;
        }           
    }
    return Arrays.copyOf(values, n);        
}
}
0
HARISH