web-dev-qa-db-fra.com

Une compréhension intuitive du heapsort?

À l'école, nous apprenons actuellement des algorithmes de tri en Java et j'ai obtenu pour mes devoirs le tri par tas. J'ai fait ma lecture, j'ai essayé d'en savoir autant que possible, mais il semble que je ne peut pas saisir le concept.

Je ne vous demande pas de m'écrire un programme Java, si vous pouviez m'expliquer aussi simplement que possible le fonctionnement du tri par tas.

42
Rok Novosel

Bon, donc en gros, vous prenez un tas et retirez le premier nœud du tas - car le premier nœud est garanti être le plus grand/le plus petit selon la direction du tri. La chose délicate est de rééquilibrer/créer le tas en premier lieu.

Deux étapes ont été nécessaires pour que je comprenne le processus du tas - d'abord, je pense à cela comme un arbre, je m'y mets en tête, puis je transforme cet arbre en un tableau afin qu'il puisse être utile.

La deuxième partie consiste à traverser essentiellement la largeur de l'arbre en premier, de gauche à droite en ajoutant chaque élément dans le tableau. Donc, l'arbre suivant:

                                    73                          
                                 7      12          
                               2   4  9   10    
                             1          

Serait {73,7,12,2,4,9,10,1}

La première partie nécessite deux étapes:

  1. Assurez-vous que chaque nœud a deux enfants (à moins que vous n'ayez pas assez de nœuds pour le faire comme dans l'arborescence ci-dessus.
  2. Assurez-vous que chaque nœud est plus grand (ou plus petit si le tri est en premier) que ses enfants.

Donc, pour accumuler une liste de nombres, vous ajoutez chacun au tas, puis en suivant ces deux étapes dans l'ordre.

Pour créer mon tas ci-dessus, j'ajouterai 10 en premier - c'est le seul nœud donc rien à faire. Ajoutez 12 comme enfant à gauche:

    10
  12

Cela satisfait 1, mais pas 2, je vais donc les échanger:

    12
  10

Ajouter 7 - rien à faire

    12
  10  7

Ajouter 73

          12
       10     7
    73

10 <73, il faut donc les échanger:

          12
       73     7
    10

12 <73, il faut donc les échanger:

          73
       12     7
    10

Ajouter 2 - rien à faire

          73
       12     7
    10   2

Ajouter 4 - rien à faire

          73
       12     7
    10   2  4

Ajouter 9

          73
       12     7
    10   2  4   9

7 <9 - échange

          73
       12     9
    10   2  4   7

Ajouter 1 - rien à faire

          73
       12     9
    10   2  4   7
  1

Nous avons notre tas: D

Maintenant, vous supprimez simplement chaque élément du haut, en échangeant le dernier élément vers le haut de l'arborescence à chaque fois, puis en rééquilibrant l'arbre:

Enlevez 73 - mettez 1 à sa place

          1
       12     9
    10   2  4   7

1 <12 - alors échangez-les

          12
        1    9
    10   2  4   7

1 <10 - alors échangez-les

          12
       10     9
     1   2  4   7

Enlever 12 - remplacer par 7

          7
       10     9
     1   2  4   

7 <10 - échangez-les

          10
       7     9
     1   2  4   

Enlever 10 - remplacer par 4

          4
       7     9
    1   2  

4 <7 - échange

          7
       4     9
    1   2  

7 <9 - échange

          9
       4     7
    1   2 

Enlever 9 - remplacer par 2

          2
       4     7
    1   

2 <4 - échangez-les

          4
       2     7
    1  

4 <7 - échangez-les

          7
       2     4
    1  

Enlever 7 - remplacer par 1

          1
       2     4

1 <4 - échangez-les

          4
       2     1

Prendre 4 - remplacer par 1

          1
       2

1 <2 - échangez-les

          2
       1

Prendre 2 - remplacer par 1

          1

Prenez 1

Liste triée voila.

120
Matt Fellows

Une façon de penser le tri en tas est une version intelligemment optimisée du tri par sélection. Dans le tri par sélection, le tri fonctionne en trouvant à plusieurs reprises le plus grand élément non encore placé correctement, puis en le plaçant au bon endroit suivant dans le tableau. Cependant, le tri par sélection s'exécute dans le temps O (n2) car il doit effectuer n cycles pour trouver le plus grand élément d'un groupe (et il peut y avoir jusqu'à n éléments différents à examiner) et le mettre en place.

Intuitivement, le tri de tas fonctionne en créant une structure de données spéciale appelée tas binaire qui accélère la recherche du plus grand élément parmi les éléments de tableau non placés. Les tas binaires prennent en charge les opérations suivantes:

  • Insert , qui insère un élément dans le tas, et
  • Delete-Max , qui supprime et renvoie le plus grand élément du tas.

À un niveau très élevé, l'algorithme fonctionne comme suit:

  • Insérez chaque élément du tableau dans un nouveau tas binaire.
  • Pour i = n jusqu'à 1:
    • Appelez Delete-Max sur le tas pour récupérer le plus grand élément du tas.
    • Écrivez cet élément à la position i.

Cela trie le tableau car les éléments retournés par Delete-Max sont dans l'ordre décroissant. Une fois tous les éléments supprimés, le tableau est ensuite trié.

Le tri en tas est efficace car les opérations Insert et Delete-Max sur un le tas s'exécute en temps O (log n), ce qui signifie que n insertions et suppressions peuvent être effectuées sur le tas en temps O (n log n). ne analyse plus précise peut être utilisé pour montrer qu'en fait, cela prend du temps Θ (n log n) quel que soit le tableau d'entrée.

En règle générale, le tri en tas utilise deux optimisations majeures. Tout d'abord, le tas est généralement construit en place à l'intérieur du tablea en traitant le tableau lui-même comme une représentation compressée du tas. Si vous regardez une implémentation heapsort, vous verrez généralement des utilisations inhabituelles des indices de tableau basés sur la multiplication et la division par deux; ces accès fonctionnent car ils traitent le tableau comme une structure de données condensée. Par conséquent, l'algorithme ne nécessite que O(1) espace de stockage auxiliaire.

Deuxièmement, plutôt que de construire le tas un élément à la fois, le tas est généralement construit en utilisant un algorithme spécialisé qui s'exécute dans le temps Θ (n) pour construire le tas sur place. Fait intéressant, dans certains cas, cela finit par rendre le code plus facile à lire car le code peut être réutilisé, mais l'algorithme lui-même devient un peu plus difficile à comprendre et à analyser.

Vous verrez parfois un tri effectué avec un tas ternaire . Cela a l'avantage d'être légèrement plus rapide en moyenne, mais si vous trouvez une implémentation heapsort utilisant cela sans savoir ce que vous regardez, il peut être assez difficile à lire. D'autres algorithmes utilisent également la même structure générale mais une structure de tas plus complexe. Smoothsort utilise un tas beaucoup plus compliqué pour obtenir O(n) meilleur comportement tout en conservant O(1) utilisation de l'espace) et le comportement dans le pire des cas O (n log n). Tri peuplier est similaire à smoothsort, mais avec une utilisation de l'espace O (log n) et des performances légèrement meilleures. On peut même penser à des algorithmes de tri classiques comme - tri par insertion et tri par sélection en tant que variantes de tri par tas .

Une fois que vous maîtrisez mieux le tri sélectif, vous pouvez envisager l'algorithme introsort , qui combine le tri rapide, le tri sélectif et le tri par insertion pour produire un algorithme de tri extrêmement rapide qui combine la force du tri rapide (rapide tri moyen), heapsort (excellent comportement dans le pire des cas) et tri par insertion (tri rapide pour les petits tableaux). Introsort est ce qui est utilisé dans de nombreuses implémentations de std::sort, et n'est pas très difficile à implémenter une fois que vous avez un heapsort fonctionnel.

J'espère que cela t'aides!

31
templatetypedef

Supposons que vous ayez une structure de données spéciale (appelée un tas) qui vous permet de stocker une liste de nombres et de récupérer et supprimer le plus petit en O(lg n) temps.

Voyez-vous comment cela conduit à un algorithme de tri très simple?

La partie difficile (ce n'est en fait pas si difficile) implémente le tas.

2
tskuzzy

Peut-être que le traçage interactif vous aidera à mieux comprendre l'algorithme. Voici une démo .

1
tenorsax

Je vais voir comment je vais y répondre, car mon explication pour le tri de tas et ce qu'est un tas sera un peu ...

... euh, terrible .

Quoi qu'il en soit, tout d'abord, nous ferions mieux de vérifier ce qu'est un tas:

Tiré de Wikipedia , un tas est:

En informatique, un tas est une structure de données arborescente spécialisée qui satisfait la propriété du tas: si B est un nœud enfant de A, alors clé (A) ≥ clé (B). Cela implique qu'un élément avec la plus grande clé est toujours dans le nœud racine, et donc un tel tas est parfois appelé un max-tas. (Alternativement, si la comparaison est inversée, le plus petit élément se trouve toujours dans le nœud racine, ce qui donne un tas min.)

À peu près, un tas est un arbre binaire tel que tous les enfants d'un nœud sont plus petits que ce nœud.

Maintenant, le tri en tas est un algorithme de tri O ( n lg (n) ). Vous pouvez en lire un peu ici et ici . Cela fonctionne à peu près en mettant tous les éléments de tout ce que vous essayez de trier dans un tas, puis en construisant le tableau trié du plus grand élément au plus petit. Vous continuerez à restructurer le tas, et puisque le plus grand élément est en haut (racine) du tas à tout moment, vous pouvez simplement continuer à prendre cet élément et à le placer à l'arrière du tableau trié. (Autrement dit, vous allez créer le tableau trié à l'envers)

Pourquoi cet algorithme est-il O ( n lg (n) )? Parce que toutes les opérations sur un tas sont O ( lg (n) ) et par conséquent, vous ferez n opérations, résultant en un temps de fonctionnement total de O ( n lg (n) ).

J'espère que ma terrible diatribe t'a aidé! C'est un peu verbeux; Désolé pour ça...

1
blahman

Le tri de tas comprend la logique la plus simple avec une complexité temporelle O(nlogn) et une complexité spatiale O (1)

 public class HeapSort {

public static void main(String[] args) {
     Integer [] a={12,32,33,8,54,34,35,26,43,88,45};

     HeapS(a,a.length-1);

    System.out.println(Arrays.asList(a));

}

private static void HeapS(Integer[] a, int l) {


    if(l<=0)
        return;

    for (int i = l/2-1; i >=0 ; i--) {

        int index=a[2*i+1]>a[2*i+2]?2*i+1:2*i+2;
        if(a[index]>a[i]){
            int temp=a[index];
            a[index]=a[i];
            a[i]=temp;
        }

    }
    int temp=a[l];
    a[l]=a[0];
    a[0]=temp;

    HeapS(a,l-1);

  }
}
0
brahmananda Kar

Je me souviens de mon professeur d'analyse d'algorithmes pour nous dire que l'algorithme de tri en tas fonctionne comme un tas de gravier:

Imaginez que vous avez un sac rempli de gravier et que vous le videz sur le sol: les grosses pierres rouleront probablement vers le bas et les petites pierres (ou sable) resteront sur le dessus.

Vous prenez maintenant le haut du tas et l'enregistrez à la plus petite valeur de votre tas. Remettez le reste de votre tas dans votre sac et répétez. (Ou vous pouvez obtenir l'approche opposée et saisir la plus grosse pierre que vous avez vue rouler sur le sol, l'exemple est toujours valable)

C'est plus ou moins la manière simple que je connais pour expliquer comment fonctionne le tri en tas.

0
STT LCU