Quelle est la différence entre un arbre de recherche binaire et un tas binaire?

92

Ces deux semblent très similaires et ont une structure presque identique. Quelle est la différence? Quelles sont les complexités temporelles pour les différentes opérations de chacune?

piperchester
la source

Réponses:

63

Heap garantit simplement que les éléments des niveaux supérieurs sont supérieurs (pour max-heap) ou plus petits (pour min-heap) que ceux des niveaux inférieurs, alors que BST garantit l'ordre (de "gauche" à "droite"). Si vous voulez des éléments triés, utilisez BST. par Dante n'est pas un geek

Heap est meilleur à findMin / findMax (O (1)), tandis que BST est bon à toutes les découvertes (O (logN)). Insert est O (logN) pour les deux structures. Si vous ne vous souciez que de findMin / findMax (par exemple, lié à la priorité), optez pour heap. Si vous voulez tout régler, allez avec BST.

par xysun

Ali786
la source
Je pense que BST est meilleur dans findMin & findMax stackoverflow.com/a/27074221/764592
Yeo
10
Je pense que ceci est juste une idée fausse commune. Un arbre binaire peut être facilement modifié pour trouver les valeurs min et max indiquées par Yeo. C'est en fait une restriction du tas: la seule découverte efficace est min ou max. Le véritable avantage du tas est O (1) d'insertion moyenne comme je l' explique: stackoverflow.com/a/29548834/895245
Ciro Santilli新疆改造中心法轮功六四事件
Selon cette vidéo , vous pouvez avoir des valeurs plus grandes à un niveau inférieur, à condition que le plus grand ne descende pas du niveau inférieur.
Whoan
Le tas est trié de la racine à la feuille et le BST est trié de gauche à droite.
Deep Joshi
34

Les arbres de recherche binaires et les tas binaires sont des structures de données basées sur des arbres.

Les tas exigent que les nœuds aient la priorité sur leurs enfants. Dans un tas max, les enfants de chaque nœud doivent être inférieurs à lui-même. C'est le contraire pour un tas min:

Binary Max Heap

Les arbres de recherche binaires (BST) suivent un ordre spécifique (précommande, en ordre, post-commande) entre les nœuds frères. L'arbre doit être trié, contrairement aux tas:

Arbre de recherche binaire

O(bûchen)
O(1)O(bûchen)

piperchester
la source
1
O(bûchen)
32

Sommaire

          Type      BST (*)   Heap
Insert    average   log(n)    1
Insert    worst     log(n)    log(n) or n (***)
Find any  worst     log(n)    n
Find max  worst     1 (**)    1
Create    worst     n log(n)  n
Delete    worst     log(n)    log(n)

Tous les temps moyens sur cette table sont les mêmes que leurs temps les pires à l'exception de Insert.

  • *: partout dans cette réponse, BST == BST équilibré, car déséquilibré aspire asymptotiquement
  • **: en utilisant une modification triviale expliquée dans cette réponse
  • ***: log(n)pour tas de pointeur, npour tas dynamique

Avantages du tas binaire sur un BST

Avantage de la BST sur le tas binaire

  • la recherche d'éléments arbitraires est O(log(n)). C'est la caractéristique qui tue les BST.

    Pour tas, c'est O(n)en général, à l'exception du plus grand élément qui est O(1).

"Faux" avantage du tas sur BST

  • tas est O(1)de trouver max, BST O(log(n)).

    Ceci est une idée fausse commune, car il est trivial de modifier un fichier BST pour garder le suivi du plus grand élément et de le mettre à jour chaque fois que cet élément peut être modifié: lors de l'insertion d'un plus grand échange, le deuxième plus grand à la suppression. https://stackoverflow.com/questions/7878622/can-we-use-binary-search-tree-to-simulate-heap-operation (mentionné par Yeo ).

    En réalité, il s’agit d’une limitation des tas comparée aux BST: la seule recherche efficace est celle du plus gros élément.

L'insertion moyenne de tas binaire est O(1)

Sources:

Argument intuitif:

  • les niveaux inférieurs des arbres ont exponentiellement plus d'éléments que les niveaux supérieurs, de sorte que de nouveaux éléments sont presque sûrs d'aller au bas
  • l'insertion de tas commence par le bas , BST doit commencer par le haut

Dans un segment binaire, augmenter la valeur à un index donné revient également O(1)à la même raison. Mais si vous souhaitez le faire, il est probable que vous souhaitiez conserver un index supplémentaire à jour sur les opérations de segment de mémoire https://stackoverflow.com/questions/17009056/how-to-implement-ologn-decrease- clé-operation-for-min-heap-based-priority-queu, par exemple pour Dijkstra. Possible sans coût supplémentaire de temps.

Indice de référence d'insertion de bibliothèque standard GCC C ++ sur du matériel réel

J'ai comparé les insertions C ++ std::set( BST arbre rouge-noir ) et std::priority_queue( tas de tableau dynamique ) pour voir si j'avais raison concernant les temps d'insertion, et voici ce que j'ai obtenu:

entrez la description de l'image ici

  • code de référence
  • script d'intrigue
  • données de parcelle
  • testé sur Ubuntu 19.04, GCC 8.3.0 sur un ordinateur portable Lenovo ThinkPad P51 avec processeur: Processeur Intel Core i7-7820HQ (4 cœurs / 8 threads, base de 2,90 GHz, mémoire cache de 8 Mo), RAM: 2x Samsung M471A2K43BB1-CRC (2x 16 Gb) , 2400 Mbps), SSD: Samsung MZVLB512HAJQ-000L7 (512 Go, 3 000 Mo / s)

Alors clairement:

Indice de référence d'insertion de bibliothèque standard GCC C ++ sur gem5

gem5 est un simulateur de système complet, et fournit donc une horloge infiniment précise avec m5 dumpstats. J'ai donc essayé de l'utiliser pour estimer le minutage d'insertions individuelles.

entrez la description de l'image ici

Interprétation:

  • heap est toujours constant, mais nous voyons maintenant plus en détail qu'il y a quelques lignes et que chaque ligne la plus haute est plus clairsemée.

    Cela doit correspondre aux latences d'accès mémoire effectuées pour les insertions de plus en plus hautes.

  • TODO Je ne peux pas vraiment interpréter complètement le BST car il n’a pas l’air si logarithmique et un peu plus constant.

    Avec ces détails plus détaillés, nous pouvons toutefois voir quelques lignes distinctes, mais je ne suis pas sûr de ce qu’elles représentent: je m'attendrais à ce que la ligne de fond soit plus fine, puisque nous insérons en haut en bas?

Benchmarked avec cette installation Buildroot sur un processeur HPI aarch64 .

BST ne peut pas être efficacement implémenté sur un tableau

Les opérations de segment de mémoire n'ont besoin que de bouillonner vers le haut ou le bas d'une seule branche d'arborescence. Dans le O(log(n))pire des cas, les échanges sont donc O(1)moyens.

Garder un BST équilibré nécessite des rotations d’arbres, ce qui peut remplacer l’élément supérieur par un autre et nécessiter le déplacement de tout le tableau ( O(n)).

Les tas peuvent être efficacement implémentés sur un tableau

Les index parents et enfants peuvent être calculés à partir de l'index actuel, comme indiqué ici .

Il n'y a pas d'opérations d'équilibrage comme BST.

Supprimer min est l'opération la plus inquiétante car elle doit être descendante. Mais cela peut toujours être fait en "infiltrant" une seule branche du tas, comme expliqué ici . Cela conduit à un cas pire O (log (n)), car le tas est toujours bien équilibré.

Si vous insérez un seul nœud pour chaque nœud supprimé, vous perdez l'avantage de l'insertion moyenne asymptotique O (1) fournie par les tas car la suppression l'emporterait, et vous pourriez également utiliser un fichier BST. Cependant, Dijkstra met à jour les nœuds plusieurs fois pour chaque suppression, donc tout va bien.

Tas de tableaux dynamiques vs tas de pointeurs

Les tas peuvent être efficacement mis en œuvre au-dessus des tas de pointeurs: https://stackoverflow.com/questions/19720438/is-it-possible-to-make-efficient-pointer-based-binary-heap-implementations

L'implémentation de tableau dynamique est plus efficace en termes d'espace. Supposons que chaque élément de tas ne contienne qu'un pointeur sur un struct:

  • l'implémentation de l'arborescence doit stocker trois pointeurs pour chaque élément: parent, enfant gauche et enfant droit. Donc, l'utilisation de la mémoire est toujours 4n(3 pointeurs d'arbre + 1 structpointeur).

    Les BST d'arbres auraient également besoin d'informations d'équilibrage supplémentaires, telles que le noir-rouge.

  • l'implémentation de tableau dynamique peut être de taille 2njuste après un doublement. Donc, en moyenne, ça va être 1.5n.

D'autre part, le tas d'arborescence a une meilleure insertion dans le pire des cas, car copier le tableau dynamique de sauvegarde pour doubler sa taille prend le O(n)pire des cas, alors que le tas d'arborescence ne fait que de nouvelles petites allocations pour chaque nœud.

Néanmoins, le doublement de la matrice de support est O(1)amorti, ce qui revient à une considération de latence maximale. Mentionné ici .

Philosophie

  • Les BST conservent une propriété globale entre un parent et tous les descendants (gauche plus petite, droite plus grande).

    Le nœud supérieur d'un BST est l'élément central, ce qui nécessite une connaissance globale pour sa maintenance (savoir combien d'éléments plus petits et plus grands sont présents).

    Cette propriété globale est plus coûteuse à gérer (log n insert), mais donne des recherches plus puissantes (log n search).

  • Les tas gardent une propriété locale entre le parent et les enfants directs (parent> enfants).

    La note supérieure d'un segment de mémoire est le gros élément, qui ne nécessite que des connaissances locales à maintenir (connaître votre parent).

Liste doublement chaînée

Une liste doublement chaînée peut être vue comme un sous-ensemble du tas où le premier élément a la priorité la plus grande, comparons-les ici aussi:

  • insertion:
    • position:
      • Liste doublement chaînée: l'élément inséré doit être le premier ou le dernier, car nous ne disposons que de pointeurs sur ces éléments.
      • tas binaire: l'élément inséré peut se retrouver dans n'importe quelle position. Moins restrictif que la liste chaînée.
    • temps:
      • liste doublement chaînée: O(1)pire des cas puisque nous avons des pointeurs sur les éléments, et la mise à jour est vraiment simple
      • tas binaire: O(1)moyen, donc pire que la liste chaînée. Compromis pour avoir une position d'insertion plus générale.
  • recherche: O(n)pour les deux

Un cas d'utilisation pour cela est lorsque la clé du tas est l'horodatage actuel: dans ce cas, les nouvelles entrées iront toujours au début de la liste. Ainsi, nous pouvons même oublier l'horodatage exact, et garder la position dans la liste comme priorité.

Ceci peut être utilisé pour implémenter un cache LRU . Comme pour les applications de type tas telles que Dijkstra , vous souhaiterez conserver un hashmap supplémentaire de la clé au nœud correspondant de la liste, afin de rechercher le nœud à mettre à jour rapidement.

Comparaison de différents BST équilibrés

Bien que l'insertion asymptotique et les temps de recherche de toutes les structures de données classées comme "BST équilibrées" que j'ai vus jusqu'à présent soient identiques, différents BBST ont des compromis différents. Je n'ai pas encore étudié cela à fond, mais il serait bon de résumer ces compromis ici:

  • Arbre rouge-noir . Il semble que ce soit le BBST le plus couramment utilisé à partir de 2019, par exemple, celui utilisé par la mise en œuvre de GCC 8.3.0 C ++.
  • Arbre AVL . Cela semble être un peu plus équilibré que BST, il pourrait donc être préférable de rechercher une latence, au prix de recherches un peu plus coûteuses. Wiki résume: "Les arbres AVL sont souvent comparés aux arbres rouge-noir, car ils prennent en charge le même ensemble d'opérations et prennent [le même] temps que les opérations de base. Pour les applications exigeant une recherche intensive, les arbres AVL sont plus rapides que les arbres rouge-noir, car ils sont plus strictement équilibrés. Comme les arbres rouge-noirs, les arbres AVL sont équilibrés en hauteur. En général, ils ne sont ni équilibrés en poids, ni en poids pour tous les mu <1/2, c’est-à-dire que les noeuds frères peuvent avoir énormément nombre différent de descendants ".
  • WAVL . Le document original mentionne les avantages de cette version en termes de limites pour les opérations de rééquilibrage et de rotation.

Voir également

Question similaire sur CS: Quelle est la différence entre un arbre de recherche binaire et un tas binaire?

Ciro Santilli 改造 中心 六四 事件
la source
1
Très bonne réponse. Les applications courantes de tas sont les éléments médian, kmin, k premiers. Pour ces opérations les plus courantes, supprimez min puis insérez (nous avons généralement un petit tas avec peu d’opérations d’insertion pures). Cela ressemble donc à la pratique, pour ces algorithmes, il ne surpasse pas BST.
Yura
1
Réponse exceptionnelle !!! En utilisant deque comme structure de tas sous-jacente, vous pouvez réduire considérablement les temps de redimensionnement, bien que ce soit toujours le cas le plus défavorable, car il faut réallocer un tableau (plus petit) de pointeurs sur des morceaux.
Bulat
13

Avec la structure de données, il faut distinguer les niveaux de préoccupation.

  1. Les structures de données abstraites (objets stockés, leurs opérations) dans cette question sont différentes. L'un implémente une file d'attente prioritaire, l'autre un ensemble. Une file d'attente prioritaire n'est pas intéressée par la recherche d'un élément arbitraire, mais uniquement de celui dont la priorité est la plus grande.

  2. La mise en œuvre concrète des structures. À première vue, ce sont tous deux des arbres (binaires), avec des propriétés structurelles différentes. L'ordre relatif des clés et les structures globales possibles diffèrent. (Un peu imprécis, dans une BSTclé, les commandes sont ordonnées de gauche à droite, dans un segment, elles sont ordonnées de haut en bas.) Comme le remarque correctement IPlant, un segment doit également être "complet".

  3. Il y a une différence finale dans la mise en œuvre de bas niveau . Un arbre de recherche binaire (non équilibré) a une implémentation standard utilisant des pointeurs. Un tas binaire, au contraire, a une implémentation efficace utilisant un tableau (précisément à cause de la structure restreinte).

Hendrik Jan
la source
1

En plus des réponses précédentes, le tas doit avoir la propriété de structure de tas; l'arbre doit être plein, et la couche la plus basse, qui ne peut pas toujours être pleine, doit être remplie de gauche à droite, sans espace vide.

lPlant
la source