Quelle serait la meilleure solution pour trouver les N premiers éléments (disons 10) dans une liste non ordonnée (par exemple 100).
La solution qui m'est venue à l’esprit était de: 1. trier le tout en utilisant le tri rapide, 2. obtenir le top 10.
Mais existe-t-il une meilleure alternative?
Le temps pourrait être réduit au temps linéaire:
Utilisez l’algorithme selection , qui trouve efficacement le k-ème élément d’un tableau non trié en temps linéaire. Vous pouvez utiliser une variante de tri rapide ou des algorithmes plus robustes.
Obtenez le top k en utilisant le pivot obtenu à l’étape 1.
Que diriez-vous de tout déléguer à 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
}
Je n'essaie pas de dire que c'est la meilleure façon. Je pense toujours que la méthode de Yin Zhu pour trouver le kème élément le plus grand est la meilleure réponse.
Si vous traitez avec des éléments simples tels que des entiers de longueur fixe, à condition que vous puissiez économiser une mémoire tampon de la même taille que les données d'entrée, le tri peut être effectué en O(n) à l'aide de tris finis ou à bases. et ce sera le plus rapide.
Bien qu'il existe des algorithmes de sélection temporelle linéaire, la constante cachée est très élevée - environ 24. Cela signifie qu'un algorithme O (nlog n) sera généralement plus rapide pour moins de plusieurs millions d'éléments.
Sinon, dans le cas général où vous ne pouvez comparer que deux éléments et déterminer lequel est le plus important, le problème est mieux résolu par une structure de données heap .
Supposons que vous vouliez avoir le top k de n éléments. Toutes les solutions basées sur le tri complet des données requièrent O (nlog n), alors que l'utilisation d'un segment de mémoire ne nécessite que O (nlog k): créez simplement un segment sur les k premiers éléments, puis continuez à ajouter un élément et à supprimer le maximum. Cela vous laissera avec un tas contenant les k plus petits éléments.
Oui, vous pouvez le faire dans O(n) en conservant simplement une liste de classement (triée) du premier N. Vous pouvez trier la liste en utilisant les fonctions de bibliothèque habituelles ou un réseau de tri . Par exemple. une simple démonstration utilisant 3 et indiquant quels éléments de la liste en cours changent à chaque itération.
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
La meilleure solution consiste à utiliser les installations de votre langue choisie qui vous faciliteront la vie.
Cependant, en supposant qu'il s'agisse d'une question davantage liée à l'algorithme que vous devriez choisir, je vais suggérer une approche différente ici. Si vous parlez de 10 à 100, vous ne devriez généralement pas trop vous soucier de la performance, sauf si vous voulez le faire plusieurs fois par seconde.
Par exemple, ce code C (qui est à peu près aussi inefficace que je puisse le faire sans être idiot) prend encore moins d’un dixième de seconde à exécuter. Ce n'est pas assez de temps pour que je pense même à aller prendre un café.
#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;
}
Son exécution à travers time
vous donne (j'ai légèrement formaté la sortie pour la rendre lisible, mais les résultats ne sont pas affectés):
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
Une fois que les nombres deviennent importants, vous devez vous inquiéter. Ne vous méprenez pas, je ne dis pas que vous ne devriez pas penser à propos de la performance. Ce que vous ne devriez pas faire, c'est passer trop de temps à optimiser des choses qui ne comptent pas - YAGNI et tout ce jazz.
Comme pour toutes les questions d’optimisation, ne pas deviner!
Eh bien, vous pouvez créer un segment de mémoire à partir d’un tableau non trié dans le temps O(n), et vous pouvez obtenir l’élément supérieur du segment de mémoire dans le temps O(log(n)). Votre temps d’exécution total est donc O (n + k * log (n)).
Ecrit ci-dessous les implémentations de tri par sélection et par tri par insertion. Pour un plus grand ensemble de données, je suggère un tri par insertion meilleur que par tri par sélection
public interface FindTopValues
{
int[] findTopNValues(int[] data, int n);
}
Mise en œuvre du tri par insertion:
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);
}
}
Sélection Tri Mise en oeuvre:
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);
}
}
Vous pouvez utiliser List
et peut utiliser la classe Comparators
pour obtenir les résultats souhaités. C'est une solution hautement optimisée. S'il vous plaît voir un exemple ci-dessous, qui obtient les 5 meilleurs chiffres. Api peut être trouvé ici .
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);
}
}
Sortie: [9, 8, 7, 6, 5]
On m'a demandé le même algorithme pour l'interview ... Je l'ai fait, si quelqu'un peut comparer cela avec l'algorithme le plus rapide en Java - sera très utile.
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;
}
et tester pour cela:
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;
}
Le résultat est quelque chose comme:
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 résultat moyen, pour obtenir 1000 entiers maxi d'un tableau de 100.000.000 éléments initiaux . Pas mal!
Je viens d'essayer cet ensemble d'en haut:
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]
Oui, il existe un moyen de faire mieux que le tri rapide. Comme l'a souligné Yin Zhu, vous pouvez commencer par rechercher le kème élément le plus grand, puis utiliser cette valeur d'élément comme pivot pour fractionner le tableau.
Le meilleur algorithme dépend en grande partie de la taille de K. Si K est petit, il suffit alors de suivre l'algorithme de BubbleSort et d'itérer la boucle externe K fois. La complexité sera O (n * k).
Cependant, pour des valeurs de K proches de n, la complexité approchera de O (n ^ 2). Dans un tel scénario, le tri rapide pourrait être une bonne alternative.
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);
}
}