web-dev-qa-db-fra.com

Pourquoi le pire des cas de fusion est-il le temps d'exécution O (n log n)?

Quelqu'un peut-il m'expliquer en anglais simple ou un moyen facile de l'expliquer?

50
adit

Sur un tri de fusion "traditionnel", chaque passage dans les données double la taille des sous-sections triées. Après la première passe, le fichier sera trié en sections de longueur deux. Après le deuxième passage, longueur quatre. Puis huit, seize, etc. jusqu'à la taille du fichier.

Il est nécessaire de doubler la taille des sections triées jusqu'à ce qu'il y ait une section comprenant le fichier entier. Il faudra lg (N) doubler la taille de la section pour atteindre la taille du fichier, et chaque passage des données prendra un temps proportionnel au nombre d'enregistrements.

31
supercat

Le Merge Sort utilise l'approche Divide-and-Conquer pour résoudre le problème problème de tri. Tout d'abord, il divise l'entrée en deux en utilisant la récursivité. Après la division, il trie les moitiés et les fusionne en une seule sortie triée. Voir la figure

MergeSort recursion tree

Cela signifie qu'il vaut mieux trier d'abord la moitié de votre problème et faire un simple sous-programme de fusion. Il est donc important de connaître la complexité du sous-programme de fusion et le nombre de fois qu'il sera appelé dans la récursivité.

Le pseudo-code pour le tri de fusion est vraiment simple.

# C = output [length = N]
# A 1st sorted half [N/2]
# B 2nd sorted half [N/2]
i = j = 1
for k = 1 to n
    if A[i] < B[j]
        C[k] = A[i]
        i++
    else
        C[k] = B[j]
        j++

Il est facile de voir que dans chaque boucle, vous aurez 4 opérations: k ++ , i ++ ou j ++ , l'instruction if et l'instruction attribution C = A | B . Vous aurez donc moins ou égal à 4N + 2 opérations donnant une complexité O (N). Pour la preuve, 4N + 2 sera traité comme 6N, car cela est vrai pour N = 1 ( 4N +2 <= 6N ).

Supposons donc que vous avez une entrée avec [~ # ~] n [~ # ~] éléments et supposez [~ # ~] n [~ # ~] est une puissance de 2. À chaque niveau, vous avez deux fois plus de sous-problèmes avec une entrée avec des demi-éléments de l'entrée précédente. Cela signifie qu'au niveau j = 0, 1, 2, ..., lgN il y aura 2 ^ j sous-problèmes avec une entrée de longueur N/2 ^ j . Le nombre d'opérations à chaque niveau j sera inférieur ou égal à

2 ^ j * 6 (N/2 ^ j) = 6N

Notez que peu importe le niveau, vous aurez toujours des opérations 6N inférieures ou égales.

Puisqu'il y a des niveaux de lgN + 1, la complexité sera

O(6N * (lgN + 1)) = O(6N*lgN + 6N) = O(n lgN)

Les références:

65
Davi Sampaio

En effet, que ce soit dans le pire des cas ou dans le cas moyen, le tri par fusion divise simplement le tableau en deux moitiés à chaque étape, ce qui lui donne un composant lg (n) et l'autre composant N provient de ses comparaisons effectuées à chaque étape. Donc, la combiner devient presque O (nlg n). Qu'il s'agisse d'un cas moyen ou du pire des cas, le facteur lg (n) est toujours présent. Le facteur de repos N dépend des comparaisons effectuées qui proviennent des comparaisons effectuées dans les deux cas. Maintenant, le pire des cas est celui dans lequel N comparaisons se produisent pour une entrée N à chaque étape. Il devient donc un O (nlg n).

20
Pankaj Anand

Après avoir divisé le tableau à l'étape où vous avez des éléments uniques, c'est-à-dire les appeler sous-listes,

  • à chaque étape, nous comparons les éléments de chaque sous-liste avec sa sous-liste adjacente. Par exemple, [Réutiliser l'image de Davi]

    enter image description here

    1. À l'étape 1, chaque élément est comparé à son élément adjacent, donc n/2 comparaisons.
    2. À l'étape 2, chaque élément de la sous-liste est comparé à sa sous-liste adjacente, car chaque sous-liste est triée, cela signifie que le nombre maximal de comparaisons effectuées entre deux sous-listes est <= la longueur de la sous-liste, c'est-à-dire 2 (à l'étape 2) et 4 comparaisons au stade 3 et 8 au stade 4 car les sous-listes continuent de doubler de longueur. Ce qui signifie le nombre maximum de comparaisons à chaque étape = (longueur de la sous-liste * (nombre de sous-listes/2)) ==> n/2
    3. Comme vous l'avez observé, le nombre total d'étapes serait log(n) base 2 La complexité totale serait donc == (nombre maximal de comparaisons à chaque étape * nombre d'étapes) = = O ((n/2) * log (n)) ==> O (nlog (n))
18
blueskin

L'algorithme de fusion-tri trie une séquence S de taille n en temps O (n log n), en supposant que deux éléments de S peuvent être comparés en O(1) temps . enter image description here

8
Andrii Glukhyi

L'arbre récursif aura la profondeur log(N), et à chaque niveau de cet arbre vous ferez un travail combiné N pour fusionner deux tableaux triés.

Fusion de tableaux triés

Pour fusionner deux tableaux triés A[1,5] Et B[3,4], Il vous suffit d'itérer les deux en commençant au début, en choisissant l'élément le plus bas entre les deux tableaux et en incrémentant le pointeur de ce tableau. Vous avez terminé lorsque les deux pointeurs atteignent la fin de leurs tableaux respectifs.

[1,5] [3,4]  --> []
 ^     ^

[1,5] [3,4]  --> [1]
   ^   ^

[1,5] [3,4]  --> [1,3]
   ^     ^

[1,5] [3,4]  --> [1,3,4]
   ^      x

[1,5] [3,4]  --> [1,3,4,5]
    x     x

Runtime = O(A + B)

Fusionner l'illustration du tri

Votre pile d'appels récursifs ressemblera à ceci. Le travail commence aux nœuds inférieurs des feuilles et bouillonne.

beginning with [1,5,3,4], N = 4, depth k = log(4) = 2

  [1,5]    [3,4]     depth = k-1 (2^1 nodes) * (N/2^1 values to merge per node) == N
[1]  [5]  [3]  [4]   depth = k   (2^2 nodes) * (N/2^2 values to merge per node) == N

Ainsi, vous N travaillez à chacun des niveaux de k dans l'arborescence, où k = log(N)

N * k = N * log(N)

4
geg

Beaucoup d'autres réponses sont excellentes, mais je n'ai vu aucune mention de hauteur et profondeur lié aux exemples "d'arbre de fusion-tri". Voici une autre façon d'aborder la question en mettant l'accent sur l'arbre. Voici une autre image pour aider à expliquer: enter image description here

Juste un récapitulatif: comme d'autres réponses l'ont souligné, nous savons que le travail de fusion de deux tranches triées de la séquence s'exécute en temps linéaire (la fonction d'aide à la fusion que nous appelons à partir de la fonction de tri principale).
En regardant cet arbre, où nous pouvons considérer chaque descendant de la racine (autre que la racine) comme un appel récursif à la fonction de tri, essayons d'évaluer le temps que nous passons sur chaque nœud .. Étant donné que le découpage de la séquence et la fusion (les deux ensemble) prennent un temps linéaire, le temps d'exécution de tout nœud est linéaire par rapport à la longueur de la séquence à ce nœud.

C'est là que la profondeur de l'arbre entre en jeu. Si n est la taille totale de la séquence d'origine, la taille de la séquence à n'importe quel nœud est n/2je, où i est la profondeur. Ceci est illustré dans l'image ci-dessus. En combinant cela avec la quantité linéaire de travail pour chaque tranche, nous avons un temps d'exécution de O (n/2je) pour chaque nœud de l'arborescence. Maintenant, il suffit de résumer cela pour les n nœuds. Une façon de le faire est de reconnaître qu'il y a 2je nœuds à chaque niveau de profondeur dans l'arbre. Donc pour tout niveau, nous avons O (2je * n/2je), qui est O(n) parce que nous pouvons annuler les 2jes! Si chaque profondeur est O (n), il suffit de multiplier cela par la hauteur de cet arbre binaire, qui est logn. Réponse: O (nlogn)

reference: Structures de données et algorithmes en Python

3
trad

L'algorithme MergeSort prend trois étapes:

  1. Divide step calcule la position médiane du sous-tableau et il faut un temps constant O (1).
  2. Conquer step trie récursivement deux sous-tableaux d'environ n/2 éléments chacun.
  3. Combiner étape fusionne un total de n éléments à chaque passage nécessitant au plus n comparaisons donc il faut O (n).

L'algorithme nécessite environ logn passes pour trier un tableau de n éléments et donc la complexité temporelle totale est nlogn.

2
MisterJoyson