it-swarm.com.de

Finden Sie die Top-N-Elemente in einem Array

Was wäre die beste Lösung, um oberste N (etwa 10) Elemente in einer ungeordneten Liste (von etwa 100) zu finden.

Die Lösung, die mir in den Sinn kam, bestand darin, 1. die Sortierung mit Hilfe der schnellen Sortierung vorzunehmen, 2. die Top 10 zu erhalten. 

Aber gibt es eine bessere Alternative?

29
zengr

Die Zeit könnte auf lineare Zeit reduziert werden:

  1. Verwenden Sie den Auswahlalgorithmus , der das k-te Element in einem nicht sortierten Array in linearer Zeit effektiv findet. Sie können entweder eine Variante der schnellen Sortierung oder robustere Algorithmen verwenden. 

  2. Holen Sie sich den obersten k mit dem in Schritt 1 erhaltenen Drehpunkt. 

23
Yin Zhu

Wie wäre es, alles an Java zu delegieren;)

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
}

Ich versuche nicht zu sagen, dass dies der beste Weg ist. Ich denke immer noch, dass Yin Zhus Methode, das k-größte Element zu finden, die beste Antwort ist.

9
nuaavee

Wenn Sie mit einfachen Elementen wie Ganzzahlen mit fester Länge zu tun haben, können Sie, sofern Sie einen Speicherpuffer mit der gleichen Größe wie die Eingabedaten verwenden können, die Sortierung nach O(n) mit Bucket- oder Radix-Sortierungen durchführen und das wird das schnellste sein.

Obwohl es Algorithmen für die lineare Zeitauswahl gibt, ist die verborgene Konstante sehr hoch - um 24. Dies bedeutet, dass ein O (nlog n) -Algorithmus normalerweise für weniger als einige Millionen Elemente schneller ist.

Andernfalls wird das Problem am besten durch eine Heap-Datenstruktur gelöst, wenn Sie nur zwei Elemente vergleichen und feststellen können, welche größer ist.

Angenommen, Sie möchten die obersten k von n Elementen. Alle Lösungen, die auf dem vollständigen Sortieren der Daten basieren, benötigen O (nlog n) Zeit, während die Verwendung eines Heap nur O (nlog k) Zeit erfordert. Erstellen Sie einfach einen Heap auf den ersten k Elementen, fügen Sie dann ein Element hinzu und entfernen Sie das Maximum. Dadurch erhalten Sie einen Haufen mit den kleinsten k-Elementen.

7
j_random_hacker

Ja, Sie können dies in O(n) tun, indem Sie einfach eine (sortierte) Laufliste des obersten N beibehalten. Sie können die Laufliste mit den regulären Bibliotheksfunktionen oder einem Sorting Network sortieren. Z.B. eine einfache Demo mit 3, die zeigt, welche Elemente in der laufenden Liste jede Iteration ändern.

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

Die beste Lösung besteht darin, die von Ihrer gewählten Sprache bereitgestellten Funktionen zu verwenden, die Ihnen das Leben erleichtern.

Unter der Annahme, dass dies eine Frage war, die eher mit dem zu wählenden Algorithmus zusammenhängt, werde ich hier einen anderen Ansatz vorschlagen. Wenn Sie über 10 von 100 sprechen, sollten Sie sich im Allgemeinen nicht zu viele Gedanken über die Leistung machen, es sei denn, Sie möchten dies viele Mal pro Sekunde tun.

Zum Beispiel dauert die Ausführung dieses C-Codes (der ungefähr so ​​ineffizient ist, wie ich es machen kann, ohne dumm zu sein) noch weit unter einer Zehntelsekunde. Das ist nicht genug Zeit, um überhaupt an einen Kaffee zu denken.

#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;
}

Wenn Sie es durch time laufen lassen, erhalten Sie (ich habe die Ausgabe ein wenig formatiert, um sie lesbar zu machen, aber die Ergebnisse nicht beeinflusst):

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

Nur wenn die Zahlenmengen groß werden, sollten Sie sich normalerweise Sorgen machen. Versteh mich nicht falsch, ich sage nicht, dass du nicht denken über Leistung nachdenken sollst. Was Sie nicht tun sollten, ist zu viel Zeit damit zu verbringen, Dinge zu optimieren, die keine Rolle spielen - YAGNI und all dieser Jazz.

Wie bei allen Optimierungsfragen, Measure, rate nicht!

3
paxdiablo

Nun, Sie können einen Heap aus einem unsortierten Array in der Zeit O(n) erstellen, und Sie können das oberste Element von dem Heap in der Zeit O(log(n)) abrufen. Ihre Gesamtlaufzeit beträgt also O (n + k * log (n)). 

1
Charles Munger

Geschrieben unter Implementierungen sowohl für Auswahlsortierung als auch für Einfügungssortierung. Für größere Datensätze empfehle ich die Sortierung besser als die Sortierung

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

Insertion Sort-Implementierung:

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);        
}   

}

Auswahl Sortierung Implementierung:

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

Sie können List und die Comparators-Klasse von guava verwenden, um die gewünschten Ergebnisse zu erhalten. Es ist eine hochoptimierte Lösung. Bitte sehen Sie unten ein Beispiel, das Top 5-Nummern erhält. Api kann hier gefunden werden.

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);
    }

}

Ausgabe: [9, 8, 7, 6, 5]

1
Pritesh Mhatre

Ich wurde nach dem gleichen Algorithmus im Interview gefragt ... Ich habe das getan, wenn jemand das mit dem schnellsten Algorithmus in Java vergleichen kann - das ist sehr nützlich.

    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;
    }

und testen Sie dafür:

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;
}

Ergebnis ist so etwas wie:

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

~ 400msc durchschnittliches Ergebnis, um maximal 1000 Ganzzahlen aus einem Array von 100.000.000 Anfangselementen zu erhalten . Nicht schlecht!

Habe gerade das Set von oben aus probiert:

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

Ja, es gibt einen Weg, um besser zu sein als Quicksort. Wie von Yin Zhu gezeigt, können Sie zuerst nach dem k-größten Element suchen und dann diesen Elementwert als Drehpunkt verwenden, um das Array zu teilen

0
CodeKata

Der beste Algorithmus würde im Großen und Ganzen von der Größe von K abhängen. Wenn K klein ist, würden durch einfaches Befolgen des BubbleSort-Algorithmus und Iterieren der äußeren Schleife K-Male die K-Werte erreicht. Die Komplexität wird O (n * k) sein. 

Für Werte von K nahe n wird sich die Komplexität an O (n ^ 2) annähern. In einem solchen Szenario kann Quicksort eine gute Alternative sein. 

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