Complexité du temps et la langue C

Complexité du temps et la langue C
«Le but de cet article est de produire la complexité du temps pour construire un tas. La complexité du temps est le temps d'exécution relatif d'un code. Un tas est un arbre dans lequel tous les enfants pour chaque nœud parent sont supérieurs ou égaux en valeur au nœud parent. C'est un tas minimum (je.e., min-heap). Il y a aussi un tas maximum (max-heap), où tous les enfants de chaque nœud parent sont inférieurs ou égaux au nœud parent. Pour le reste de cet article, seul min-heap est considéré."

Le tas minimum ci-dessus décrit un tas où le nombre d'enfants par nœud parent peut être supérieur à deux. Un tas binaire est celui où le plus grand nombre d'enfants par nœud parent est deux. Un tas binaire complet est celui où chaque nœud a deux enfants, à l'exception que, au niveau le plus bas, il ne peut y avoir qu'un seul nœud de feuille sans frère; avec le reste des nœuds de feuilles les plus bas appariés et à partir de l'extrémité gauche extrême de la dernière rangée, se terminant par ce nœud à une seule feuille, qui doit être un nœud gauche.

Le tasage signifie construire (créer) un tas. Étant donné qu'un arbre où chaque nœud peut avoir un certain nombre d'enfants peut être transformé en un arbre binaire complet, le véritable objectif de cet article est de produire la complexité du temps pour tas un arbre binaire complet.

Un exemple de diagramme d'un arbre binaire complet est:

Tout nœud de feuille au niveau le plus bas qui n'a pas de frère doit être un nœud gauche. Tous les nœuds de la dernière rangée, y compris un possible nœud gauche gauche, sont «rincés» à l'extrémité gauche de la dernière ligne.

Par la définition d'un tas, un frère gauche peut être plus petit, plus grand ou égal au frère droit. L'ordre des deux frères et sœurs n'est pas spécifié.

Arbre binaire complet

L'arbre ci-dessus est un arbre binaire complet, mais ce n'est pas un arbre binaire complet. C'est aussi un tas minimum. S'il s'agissait d'un arbre binaire complet, alors tous les nœuds du dernier niveau mais auraient eu deux enfants chacun. L'arbre ci-dessus est redessiné ci-dessous comme un arbre binaire complet:

Un arbre peut être représenté par un tableau. L'arbre est lu de haut en bas, de gauche à droite, rangée par rangée; puis placé dans le tableau. Le tableau suivant montre le tableau de contenu et d'index pour cet arbre:

4 6 12 8 7 16 15 23 dix 20 18 25 nul nul nul
0 1 2 3 4 5 6 7 8 9 dix 11 12 13 14

Les valeurs cellulaires sont dans la première rangée de table. Les index basés sur zéro sont dans la deuxième ligne de table.

Relation entre un index parent et ses index des enfants

Dans l'arborescence ou le tableau correspondant, la racine est à l'index 0 pour l'indexation zéro. Que je sois la variable pour maintenir chaque index. Les enfants de la racine sont aux index 1 et 2, qui sont 0 + 1 et 0 + 2. Les enfants du nœud 1 sont aux index 3 et 4, qui sont 1 + 2 et 1 + 3. Les enfants du nœud 2 sont à l'indice 5 et 6, qui sont 2 + 3 et 2 + 4. Les enfants du nœud 3 sont à l'indice 7 et 8, qui sont 3 + 4 et 3 + 5. Les enfants du nœud 4 sont à l'indice 9 et 10, qui sont 4 + 5 et 4 + 6; et ainsi de suite.

Que je sois l'index parent pour l'instant. Ainsi, les enfants de chaque parent sont à l'index i + i + 1 et à i + i + 2, qui sont:

2i +1 et 2i +2

L'inverse doit être connu. C'est-à-dire, étant donné un index, je pour un enfant gauche ou un enfant droit, quel est l'index parent? Pour l'enfant gauche à l'index 1 et l'enfant droit à l'index 2, le parent est à l'index 0. Pour l'enfant gauche à l'index 3 et l'enfant droit à l'index 4, le parent est à l'index 1. Pour l'enfant gauche à l'index 5 et l'enfant droit à l'index 6, le parent est à l'index 2. Pour l'enfant gauche à l'index 7 et l'enfant droit à l'index 8, le parent est à l'index 3. Pour l'enfant gauche à l'index 9 et l'enfant droit à l'index 10, le parent est à l'index 4.

I cette fois, est un index d'enfant (pas un index parent). Ainsi, le parent de chaque enfant gauche est à l'index I / 2 de la division entière, et le parent de l'enfant droit, qui est le même que le parent de l'enfant gauche (frère), est au résultat entier de (i-1) / 2, je.e.:

i / 2 et (i-1) / 2

où la division est une division entière.

Il est également bon de savoir si un nœud est un enfant gauche ou un enfant droit: si la division normale de 2 a un reste, alors c'est un nœud gauche par indexation à base de zéro. Si la division normale de 2 n'a pas de reste, alors c'est un nœud droit par indexation basée sur zéro.

Le code en C, pour savoir si le nœud enfant est un nœud gauche ou un nœud droit, est:

if (i% 2 == 0)
// Le nœud est un nœud droit
autre
// nœud est le nœud gauche

Après avoir su qu'un nœud est un nœud gauche, l'index parent peut être obtenu en tant qu'entier en entier de I / 2. Après avoir su qu'un nœud est un nœud droit, l'indice parent peut être obtenu en tant qu'entier résultat de (I-1) / 2.

Hauteur d'un tas binaire complet et quelques index

Nombre total de nœuds
Remarquez du dernier diagramme complet au-dessus de celui lorsque le niveau d'un tas augmente de 1, son nombre total de nœuds double approximativement. Précisément, le niveau suivant est livré avec le nombre de nœuds qui font le nouveau total, la somme de tous les nœuds précédents, plus 1, fois 2, puis moins 1. Lorsque la hauteur est 1, il y a 1 nœud = (0 + 1) x 2 - 1 = 2 - 1 = 21 - 1. Lorsque la hauteur est 2, il y a 3 nœuds = (1 + 1) x 2 - 1 = 4 - 1 = 22 - 1. Lorsque la hauteur est 3, il y a 7 nœuds = (3 + 1) x 2 - 1 = 8 - 1 = 23 - 1. Lorsque la hauteur est 4, il y a 15 nœuds = (7 + 1) x 2 - 1 = 16 - 1 = 24 - 1. Lorsque la hauteur est de 5, il y a 31 nœuds = (15 + 1) x 2 - 1 = 32 - 1 = 25 - 1. Lorsque la hauteur est 6, il y a 63 nœuds = (31 + 1) x 2 - 1 = 64 - 1 = 26 - 1. Lorsque la hauteur est de 7, il y a 127 nœuds = (63 + 1) x 2 - 1 = 128 - 1 = 27 - 1; et ainsi de suite.

En général, lorsque la hauteur est H,

Non. de nœuds = 2h - 1

Dernier indice de nœud
Pour un arbre binaire et pour l'indexation basée sur les zéro, le dernier index est:

Dernier index = n - 1

où n est le nombre total de nœuds, ou simplement le nombre de nœuds.

Nombre de nœuds sans dernière rangée
Pour un arbre binaire complet, deux fois le nombre de nœuds pour la dernière ligne donne le nombre total de nœuds moins 1. Vu une autre manière, le nombre de nœuds pour la dernière ligne est égal au nombre de tous les nœuds précédents, fois deux, plus 1. Le nombre de nœuds sans la dernière ligne est donc:

Non. de nœuds sans dernier = résultat entier de n / 2

En effet. Par exemple: si le nombre de nœuds (total) est de 15, alors 15/2 = 7½. Le résultat entier, 7, est pris, et la moitié est jetée. Et 7 est le nombre de nœuds sans la dernière ligne - voir ci-dessus.

Index pour le premier nœud de la dernière ligne
L'indice du premier nœud de la dernière ligne devrait être connu. Pour l'indexation basée sur zéro, où le premier nœud (élément) est à l'index 0, l'index pour le premier nœud de la dernière ligne est le nombre de nœuds pour tous les nœuds sans la dernière ligne. C'est:

Résultat entier de n / 2

Notez que dans le tableau correspondant, les nœuds d'arbre sont appelés éléments.

Tamiser et tamiser

Considérez le sous-arbre suivant de trois nœuds:

Par la propriété minimale de tas, le nœud parent doit être plus petit ou égal au plus petit des nœuds d'enfants. Le nœud C doit donc être échangé avec le moindre des nœuds pour enfants; Peu importe si le moins est le frère gauche ou droit. Cela signifie que C doit être échangé avec A pour avoir:

Alors que «A» monte pour prendre la place de C, c'est-à-dire le tamisage. Alors que C descend pour prendre la place de A, c'est-à-dire le tamisage.

Illustration de tas

Le tas, comme indiqué ci-dessus, est une commande partielle, de la plus petite valeur à la plus grande valeur. Ce n'est pas une commande approfondie (pas de tri). Comme exprimé ci-dessus, le tas peut être sous forme d'arbre ou sous forme de tableau. Comme exprimé ci-dessus, le heapifiant a déjà eu lieu. En pratique, le programmeur ne trouvera pas nécessairement un arbre déjà tasé. Il trouverait une liste entièrement en désordre (complètement non triée). Cette liste désordonnée peut exister sous la forme d'un arbre ou sous la forme d'un tableau. Ce qui suit est un arbre désordonné (non trié) et son tableau correspondant:

Le tableau correspondant est:

dix 20 25 6 4 12 15 23 8 7 18 16 nul nul nul
0 1 2 3 4 5 6 7 8 9 dix 11 12 13 14

L'arbre est lu en ligne par rang, de gauche à droite et de haut en bas, tout en placé dans les cellules du tableau. Soit le nom du tableau ARR, et laissez la variable qui représente l'index basé sur le zéro. Dans cet article, la racine est à l'index 0.

Cet arbre subira un ordre partiel pour devenir un tas minimum. Une fois la commande partielle terminée, ce sera le tas minimum donné dans la section Introduction de cet article. Dans cette section, l'ordre partiel est fait manuellement.

Pour simplifier le tasage (processus de commande partiel), l'arbre binaire complet non ordonné doit être fabriqué un arbre binaire complet avant d'être traité. Pour en faire un arbre binaire complet, ajoutez des éléments dont les valeurs sont supérieures à la valeur la plus élevée déjà trouvée dans le tas non ordonné. Dans cet article, cela se fera avec le tableau et non avec la forme graphique de l'arbre.

L'élément le plus élevé (nœud) est de 25. Laissez les trois nombres ajoutés pour faire un arbre complet: 26, 27 et 28. Le tableau correspondant pour l'arbre complet devient:

dix 20 25 6 4 12 15 23 8 7 18 16 26 27 28
0 1 2 3 4 5 6 7 8 9 dix 11 12 13 14

Lorsque la liste non ordonnée sera partiellement commandée par la définition du tas, les trois derniers éléments resteront à leurs dernières positions; et peut facilement être abandonné.

Lorsque cette liste non ordonnée est partiellement commandée par la définition du tas, ce serait la liste partiellement commandée ci-dessus (introduction). Une liste partiellement commandée a un tri, mais ce n'est pas une commande complète (ce n'est pas un tri complet).

Réarrangement manuel (bâtiment du tas)

Le tableau non ordonné peut être placé comme tel:

10 20 25 06 04 12 15 23 08 07 18 16 26 27 28

Il y a 15 éléments. Le résultat entier de 15/2 est 7. La liste doit être divisée en deux moitiés, la première moitié étant 7 et la seconde moitié, 8, comme suit:

10 20 25 06 04 12 15 | 23 08 07 18 16 26 27 28

Cette division peut être considérée comme une opération principale. Le bâtiment du tas se poursuit de la droite avec les paires d'éléments, 27 et 28, identifiées comme des enfants de 15. 15 est inférieur à 27 ou 28. Ainsi, ce sous-arbre de trois nœuds satisfait la propriété de tas minimum, et les nœuds ne sont pas touchés pour l'instant. Cette vérification peut être considérée comme une autre opération principale. Il y a donc deux opérations principales jusqu'à présent (une division de tableau et un chèque).

16 et 26 sont des enfants de 12. 12 est inférieur à 16 ou 26. Ainsi, ce sous-arbre de trois nœuds satisfait la propriété de tas minimum, et les nœuds ne sont pas touchés (pour l'instant). Cette vérification peut être considérée comme une autre opération principale. Il y a donc trois opérations principales jusqu'à présent (une division et deux chèques).

07 et 18 sont les enfants de 04. 04 est inférieur à 07 ou 18. Ainsi, ce sous-arbre de trois nœuds satisfait la propriété de tas minimum, et les nœuds ne sont pas touchés (pour l'instant). Cette vérification peut être considérée comme une autre opération principale. Il y a donc quatre opérations principales jusqu'à présent (une division et trois chèques).

23 et 08 sont les enfants de 06. 06 est inférieur à 23 ou 08. Ainsi, ce sous-arbre de trois nœuds satisfait la propriété de tas minimum, et les nœuds ne sont pas touchés (pour l'instant). Cette vérification peut être considérée comme une autre opération principale. Il y a donc cinq opérations principales jusqu'à présent (une division et quatre chèques).

Les 8 éléments de la dernière rangée de l'arbre et leurs parents ont été vérifiés. Pour passer au niveau précédent, la partie gauche de l'arbre doit être divisée par 2, et le résultat entier est pris. Le résultat entier de 7/2 est 3. Le nouveau placement de la liste est:

10 20 25 | 06 04 12 15 | 23 08 07 18 16 26 27 28

Cette division peut être considérée comme une opération principale. Il y a donc six opérations principales jusqu'à présent (deux divisions et quatre chèques).

12 et 15 sont les enfants de 25 ans. 25 est supérieur à 12 ou 15. Ce sous-arbre de trois nœuds ne satisfait pas à la propriété de tas minimale, donc les nœuds doivent être touchés. Cependant, cette vérification peut toujours être considérée comme une autre opération principale. Il y a donc sept opérations principales jusqu'à présent (deux divisions et cinq chèques).

Tamiser vers le bas doit avoir lieu et peut-être jusqu'à la dernière rangée. Chaque tamis (échange) est l'opération principale.

25 est échangé avec le moins de ses enfants, 12 pour donner la configuration:

10 20 12 | 06 04 25 15 | 23 08 07 18 16 26 27 28

25 est maintenant au troisième niveau et plus au deuxième niveau. 25 est maintenant le parent de 16 et 26. À ce stade, 25 se trouve être supérieur à 16 mais moins de 26. Donc 25 et 16 sont échangés. Cet échange est une autre opération principale, et il y a donc neuf opérations principales jusqu'à présent (deux divisions, cinq chèques et deux swaps). La nouvelle configuration est:

10 20 12 | 06 04 16 15 | 23 08 07 18 25 26 27 28

Au deuxième niveau de la liste donnée, il y en avait 20 et 25. 25 a été tamisé jusqu'à la dernière rangée. 20 est encore à vérifier.

Actuellement, 06 et 04 sont des enfants de 20. 20 est supérieur à 06 ou 04. Ce sous-arbre de trois nœuds ne satisfait pas à la propriété de tas minimale, donc les nœuds doivent être touchés. Cependant, cette vérification peut toujours être considérée comme une autre opération principale. Il y a donc dix opérations principales jusqu'à présent (deux divisions, six chèques et deux swaps). Tamiser vers le bas doit avoir lieu et peut-être jusqu'à la dernière rangée. Chaque tamis (échange) est l'opération principale.

20 est échangé avec le moins de ses enfants, 04 pour donner la configuration:

10 04 12 | 06 20 16 15 | 23 08 07 18 25 26 27 28

20 est maintenant au troisième niveau et plus au deuxième niveau. 20 est maintenant le parent de 07 et 18. À ce stade, 20 se trouve être supérieur à 07 ou 18. Donc 20 et 07 sont échangés. Cet échange est une autre opération principale, et il y a donc douze opérations principales jusqu'à présent (deux divisions, six chèques et quatre swaps). La nouvelle configuration est:

10 04 12 | 06 07 16 15 | 23 08 20 18 25 26 28 28

Les tamis vers le bas du chemin précédent ont pris fin. La partie gauche qui n'a pas été complètement vérifiée doit être divisée en deux (aller au niveau précédent) pour avoir:

10 | 04 12 | 06 07 16 15 | 23 08 20 18 25 26 28 28

Le résultat entier de 3/2 est 1.

Actuellement, 04 et 12 sont les enfants de 10. 10 est supérieur à 04 mais moins de 12. Ce sous-arbre de trois nœuds ne satisfait pas à la propriété de tas minimale, donc les nœuds doivent être touchés. Cependant, cette vérification doit toujours être considérée comme une autre opération principale. Il y a donc quatorze opérations principales jusqu'à présent (trois divisions, sept chèques et quatre swaps). Tamiser vers le bas doit avoir lieu et peut-être jusqu'à la dernière rangée. Chaque tamis (échange) est l'opération principale.

10 est échangé avec le moins de ses enfants, 04 pour donner la configuration:

04 | 10 12 | 06 07 16 15 | 23 08 20 18 25 26 28 28

10 est maintenant au deuxième niveau et plus au premier niveau. 10 est maintenant le parent 06 et 07. À ce stade, 10 se trouve être supérieur à 06 ou 07. Donc 10 et 06 sont échangés. Cet échange est une autre opération principale, et il y a donc seize opérations principales jusqu'à présent (trois divisions, sept chèques et six swaps). La nouvelle configuration est:

04 | 06 12 | 10 07 16 15 | 23 08 20 18 25 26 28 28

10 est maintenant au troisième niveau et plus au deuxième niveau. 10 est maintenant le parent 23 et 08. À ce stade, 10 est inférieur à 23 mais supérieur à 08. Donc 10 et 08 sont échangés. Cet échange est une autre opération principale, et il y a donc dix-sept opérations principales jusqu'à présent (trois divisions, sept chèques et sept swaps). La nouvelle configuration est:

04 | 06 12 | 08 07 16 15 | 23 10 20 18 25 26 28 28

Eh bien, la vérification, la division et l'échange ont commencé au dernier index et ont atteint le premier index, avec toutes les conséquences des tamis à la baisse. Cela signifie que la construction de tas est complète et, dans ce cas, avec dix-sept opérations principales (trois divisions, sept chèques et sept swaps). Il y avait 15 éléments, bien que les trois derniers étaient des éléments muets nécessaires pour simplifier le bâtiment du tas.

Algorithme

Il existe différents algorithmes pour la construction de tas. L'illustration donnée ci-dessus sera la plus efficace si une valeur parent est échangée avec l'un des enfants qui sont moins et pas toujours les moindres enfants. Les étapes de l'algorithme sont:

  • Divisez le nombre total d'éléments par deux.
  • Continuez à partir de la moitié droite, en vérifiant une paire de frères et sœurs avec le parent et en échangeant si nécessaire.
  • Lorsque tous les nœuds du dernier niveau ont été vérifiés, avec un échange possible, passez au niveau précédent et répétez les deux étapes ci-dessus. L'échange est en panne, et cela peut devoir atteindre le niveau le plus bas.
  • Lorsque la racine a été vérifiée et éventuellement échangée, arrêtez.

Complexité temporelle

La complexité du temps est l'exécution relative d'un code. Dans ce cas, c'est l'exécution relative du processus de construction de tas. La complexité du temps est en fait le nombre d'opérations principales dans le code (programme).

Officiellement, la complexité temporelle de l'algorithme de cet article est censée être des opérations, où n est le nombre d'éléments dans le tableau non ordonné plus les éléments factice. Dans ce cas, n est 15. La complexité du temps de cet algorithme est donc de 15.

Pourquoi devrait-il être 15 au lieu de 17? C'est-à-dire pourquoi cela devrait être n? - Eh bien, comme la division n'est pas la partition, la durée de chaque action de division est petite et peut être négligée. Avec cela et pour l'illustration ci-dessus, le nombre d'opérations principales deviendra 14 (sept chèques et sept swaps), les 3 divisions ignorées.

De plus, si une valeur parent est échangée avec l'un des enfants moins, et pas toujours le moindre des enfants, le temps de vérification global sera réduit. Cela rendra le temps de vérification petit et donc ignoré. Avec cela et pour l'illustration ci-dessus, le nombre d'opérations principales deviendra 7 (sept swaps), les 3 divisions ignorées et les sept chèques également ignorés.

Remarque: Pour un bon programme de construction de tas, seules les opérations d'échange (tamis dans ce cas) sont considérées dans la complexité du temps. Dans ce cas, il y a 7 opérations et non 15. En gérant la complexité du temps, le nombre maximal possible d'opérations est ce qui doit être donné.

Il est possible que les 15 nœuds ci-dessus soient échangés. Ainsi, la complexité du temps pour cet exemple doit être donnée comme 15 et non 7.

La complexité temporelle de cet algorithme est donnée, en termes généraux, en tant que:

Sur)

Où n est n, c'est la notation Big-O. Cette notation utilise des majuscules O et ses parenthèses. À l'intérieur des parenthèses se trouve l'expression du nombre maximum d'éventuels opérations pour que le code (programme) termine.

Codage en c

La fonction principale C pour s'accélérer le tableau ci-dessus est:

int main (int argc, char ** argv)

int n1 = 12;
int a1 [] = 10, 20, 25, 6, 4, 12, 15, 23, 8, 7, 18, 16;
int a2 [] = 10, 20, 25, 6, 4, 12, 15, 23, 8, 7, 18, 16, 26, 27, 28;
BuildHeap (A2, N2);
for (int i = 0; i = arr [LeftIndx] && arr [LeftIndx]> = arr [droite
int temp = arr [parentIndx]; arr [PatenerIndx] = arr [droite-stydx]; arr [droite-sty] = temp;
Lastdown = droiteIndx;

else if (arr [parentIndx]> = arr [droite-stydx] && arr [droite-stydx]> = arr [LeftIndx])
int temp = arr [parentIndx]; arr [PatenerIndx] = arr [LeftIndx]; arr [LeftIndx] = temp;
Lastdown = LeftIndX;

else if (arr [parentIndx]> = arr [droiteIndx] && arr [droitedx] = arr [LeftIndx] && arr [LeftIndx] <= arr[rightIndx])
int temp = arr [parentIndx]; arr [PatenerIndx] = arr [LeftIndx]; arr [LeftIndx] = temp;
Lastdown = LeftIndX;

Retour dernier;

Il y a une fonction tamis-down. Il utiliserait la fonction swap () et tamiserait à droite au niveau le plus bas dans un chemin. C'est:

int nextIndx;
void siftdown (int arr [], int n2, int i)
int LeftIndx, droite-standx;
int ParendIndx = i;
LeftIndx = 2 * i + 1;
RightIndx = 2 * i + 2;
if (parentIndx = 0)
NextIndx = swap (arr, PatenerIndX, LeftIndx, droite -Indx);
if (nextIndx = halfindx / 2; i--)
siftdown (a2, n2, i);
N = n / 2;
if (n> = 1)
BuildHeap (A2, N);

Tous les segments de code ci-dessus peuvent être assemblés pour former un programme qui s'acquittera du tableau non ordonné.

Conclusion

L'algorithme le plus efficace pour le tas complexité du temps est:

Sur)