On m'a posé cette question d'entrevue récemment:
On vous donne un tableau qui est presque trié, en ce que chacun des éléments
N
peut être mal placé par pas plus dek
positions de l'ordre de tri correct. Trouvez un algorithme efficace dans l'espace et le temps pour trier le tableau.
J'ai une solution O(N log k)
comme suit.
Notons arr[0..n)
Pour désigner les éléments du tableau de l'index 0
(Inclus) à N
(exclusif).
arr[0..2k)
arr[0..k)
Sont dans leur position finale triée ...arr[k..2k)
peut toujours être égaré par k
!arr[k..3k)
arr[k..2k)
Sont dans leur position finale triée ...arr[2k..3k)
peut toujours être égaré par k
arr[2k..4k)
arr[ik..N)
, Alors vous avez terminé! 2k
ÉlémentsDans chaque étape, vous triez au plus 2k
Éléments dans O(k log k)
, en plaçant au moins k
éléments dans leurs positions triées finales à la fin de chaque étape. Il y a O(N/k)
étapes, donc la complexité globale est O(N log k)
.
Mes questions sont:
O(N log k)
est-elle optimale? Cela peut-il être amélioré?Comme Bob Sedgewick l'a montré dans son travail de thèse (et ses suivis), le tri par insertion écrase écrase le "tableau presque trié" ". Dans ce cas, vos asymptotiques semblent bonnes, mais si k <12, je parie que le tri par insertion gagne à chaque fois. Je ne sais pas s'il y a une bonne explication pour pourquoi le tri par insertion fonctionne si bien, mais l'endroit où chercher serait dans l'un des manuels de Sedgewick intitulé Algorithmes (il a fait de nombreuses éditions pour différentes langues).
Je n'ai aucune idée si O (N log k) est optimal, mais plus précisément, je m'en fiche vraiment - si k est petit, ce sont les facteurs constants qui importent, et si k est grand, vous pouvez aussi bien juste trier le tableau.
Le tri par insertion résoudra ce problème sans retrier les mêmes éléments.
La notation Big-O convient parfaitement à la classe d'algorithmes, mais dans le monde réel, les constantes sont importantes. Il est trop facile de perdre cela de vue. (Et je dis cela en tant que professeur qui a enseigné la notation Big-O!)
Si vous utilisez uniquement le modèle de comparaison, O (n log k) est optimal. Considérons le cas où k = n.
Pour répondre à votre autre question, oui il est possible de le faire sans trier, en utilisant des tas.
Utilisez un min-tas d'éléments 2k. Insérez d'abord 2k éléments, puis supprimez min, insérez l'élément suivant, etc.
Cela garantit O (n log k) temps et O(k) l'espace et les tas ont généralement des constantes cachées suffisamment petites.
Il a déjà été souligné que l'une des solutions optimales asymptotiquement utilise un tas min et je voulais juste fournir du code en Java:
public void sortNearlySorted(int[] nums, int k) {
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int i = 0; i < k; i++) {
minHeap.add(nums[i]);
}
for (int i = 0; i < nums.length; i++) {
if (i + k < nums.length) {
minHeap.add(nums[i + k]);
}
nums[i] = minHeap.remove();
}
}
Votre solution est bonne si k
est suffisamment grande. Il n'y a pas de meilleure solution en termes de complexité temporelle; chaque élément peut être déplacé par k
places, ce qui signifie que vous devez apprendre log2 k
des informations pour le placer correctement, ce qui signifie que vous devez faire des comparaisons log2 k
à moins - donc ça doit être une complexité d'au moins O(N log k)
.
Cependant, comme d'autres l'ont souligné, si k
est petit, les termes constants vont vous tuer. Utilisez quelque chose de très rapide par opération, comme le tri par insertion, dans ce cas.
Si vous vouliez vraiment être optimal, vous implémenteriez les deux méthodes et basculeriez de l'une à l'autre en fonction de k
.
Puisque k
est apparemment supposé être assez petit, un tri par insertion est probablement l'algorithme le plus évident et généralement accepté.
Dans un tri par insertion sur des éléments aléatoires, vous devez parcourir N éléments, et vous devez déplacer chacun une moyenne de N/2 positions, donnant ~ N * N/2 opérations totales. La constante "/ 2" est ignorée dans une caractérisation big-O (ou similaire), donnant O (N2) complexité.
Dans le cas où vous proposez, le nombre attendu d'opérations est ~ N * K/2 - mais comme k
est une constante, l'ensemble k/2
term est ignoré dans une caractérisation big-O, donc la complexité globale est O (N).