J'ai besoin de calculer la médiane en cours d'exécution:
Entrée: , , vecteur .k ( x 1 , x 2 , … , x n )
Sortie: vecteur , où est la médiane de .y i ( x i , x i + 1 , … , x i + k - 1 )
(Pas de tricherie avec des approximations; je voudrais avoir des solutions exactes. Les éléments sont de grands entiers.)
Il existe un algorithme trivial qui maintient un arbre de recherche de taille ; la durée totale d'exécution est . (Ici, un "arbre de recherche" fait référence à une structure de données efficace qui prend en charge les insertions, les suppressions et les requêtes médianes en temps logarithmique.)
Cependant, cela me semble un peu stupide. Nous apprendrons efficacement toutes les statistiques de commande dans toutes les fenêtres de taille , pas seulement les médianes. De plus, cela n'est pas trop attrayant dans la pratique, surtout si est grand (les grands arbres de recherche ont tendance à être lents, la surcharge de consommation de mémoire n'est pas anodine, l'efficacité du cache est souvent médiocre, etc.).
Pouvons-nous faire quelque chose de nettement mieux?
Y a-t-il des limites inférieures (par exemple, l'algorithme trivial est-il asymptotiquement optimal pour le modèle de comparaison)?
Edit: David Eppstein a donné une belle borne inférieure pour le modèle de comparaison! Je me demande s'il est néanmoins possible de faire quelque chose d'un peu plus intelligent que l'algorithme trivial?
Par exemple, pourrions-nous faire quelque chose dans ce sens: diviser le vecteur d'entrée en parties de taille ; trier chaque partie (en gardant une trace des positions d'origine de chaque élément); puis utiliser le vecteur trié par morceaux pour trouver efficacement les médianes en cours d'exécution sans structures de données auxiliaires? Bien sûr, ce serait toujours , mais dans la pratique, le tri des tableaux a tendance à être beaucoup plus rapide que la maintenance des arbres de recherche.
Edit 2: Saeed voulait voir quelques raisons pour lesquelles je pense que le tri est plus rapide que les opérations d'arbre de recherche. Voici des repères très rapides, pour , :
- ≈ 8s: tri de vecteurs avec éléments chacun
- ≈ 10s: trier un vecteur avec éléments
- ≈ Années 80: insertions et suppressions dans une table de hachage de taille
- ≈ 390s: insertions et suppressions dans un arbre de recherche équilibré de taille
La table de hachage est là juste pour comparaison; il n'est d'aucune utilité directe dans cette application.
En résumé, nous avons presque un facteur 50 de différence dans les performances du tri par rapport aux opérations d'arborescence de recherche équilibrée. Et les choses empirent si nous augmentons .
(Détails techniques: Données = nombres entiers aléatoires de 32 bits. Ordinateur = un ordinateur portable moderne typique. Le code de test a été écrit en C ++, en utilisant les routines de bibliothèque standard (std :: sort) et les structures de données (std :: multiset, std :: unsorted_multiset). J'ai utilisé deux compilateurs C ++ différents (GCC et Clang), et deux implémentations différentes de la bibliothèque standard (libstdc ++ et libc ++). Traditionnellement, std :: multiset a été implémenté comme un arbre rouge-noir hautement optimisé.)
la source
Réponses:
Voici une limite inférieure du tri. Étant donné un ensemble d'entrée de longueur à trier, créez une entrée pour votre problème médian en cours composé de copies d'un nombre inférieur au minimum de , puis lui-même, puis copies d'un nombre supérieur à le maximum de , et fixons . Les médianes de fonctionnement de cette entrée sont les mêmes que l'ordre de tri de .S n n−1 S S n−1 S k=2n−1 S
Ainsi, dans un modèle de comparaison de calcul, un temps est requis. Peut-être que si vos entrées sont des nombres entiers et que vous utilisez des algorithmes de tri des nombres entiers, vous pouvez faire mieux.Ω(nlogn)
la source
Edit: Cet algorithme est maintenant présenté ici: http://arxiv.org/abs/1406.1717
Oui, pour résoudre ce problème, il suffit d'effectuer les opérations suivantes:
En gros, l'idée est la suivante:
Pour chaque :i
Les listes chaînées sont juste tableaux d'index k éléments, elles sont donc légères (sauf que la localité d'accès à la mémoire est médiocre).k
Voici un exemple d'implémentation et de benchmarks:
Voici un graphique des temps de fonctionnement (pour ):n≈2⋅106
la source
Étant donné la limite de David, il est peu probable que vous puissiez faire mieux dans le pire des cas, mais il existe de meilleurs algorithmes sensibles à la sortie. Plus précisément, si dans le nombre de médianes dans le résultat, nous pouvons résoudre le problème dans le temps O ( n log m + m log n ) .m O(nlogm+mlogn)
Pour ce faire, remplacez l'arbre binaire équilibré par un arbre binaire équilibré composé uniquement des éléments qui étaient des médianes dans le passé, plus deux tas de Fibonacci entre chaque paire de médianes précédentes (une pour chaque direction), plus les nombres pour que nous puissions localiser le tas Fibonacci qui contient un élément particulier dans la commande. Ne vous embêtez jamais à supprimer des éléments. Lorsque nous insérons un nouvel élément, nous pouvons mettre à jour notre structure de données en temps . Si les nouveaux dénombrements indiquent que la médiane se trouve dans l'un des tas de Fibonacci, il faut un O supplémentaire ( log n ) pour extraire la nouvelle médiane. Ce O ( log n )O(logm) O(logn) O(logn) la charge n'a lieu qu'une fois par médiane.
la source