web-dev-qa-db-fra.com

Différence entre std :: set et std :: priority_queue

Étant donné que _std::priority_queue_ et _std::set_ (et _std::multiset_) sont des conteneurs de données qui stockent des éléments et vous permettent d'y accéder de manière ordonnée, et ont la même complexité d'insertion O(log n) , quels sont les avantages d'utiliser l'un par rapport à l'autre (ou, quel genre de situations appellent l'un ou l'autre?)?

Bien que je sache que les structures sous-jacentes sont différentes, je ne suis pas autant intéressé par la différence dans leur mise en œuvre que par la comparaison de leurs performances et adéquation pour diverses utilisations.

Note: Je connais les non-doublons dans un ensemble. C'est pourquoi j'ai également mentionné _std::multiset_ car il a exactement le même comportement que le _std::set_ mais peut être utilisé là où les données stockées sont autorisées à comparer en tant qu'éléments égaux. Alors s'il vous plaît, ne commentez pas le problème des clés simples/multiples.

56
penelope

Une file d'attente prioritaire niquement vous donne accès à l'élément n dans l'ordre trié - c'est-à-dire que vous pouvez obtenir l'élément de priorité la plus élevée, et lorsque vous le supprimez, vous pouvez obtenir le suivant priorité la plus élevée, etc. Une file d'attente prioritaire permet également de dupliquer des éléments, c'est donc plus comme un multiset qu'un ensemble. [Edit: Comme l'a souligné @Tadeusz Kopec, la construction d'un tas est également linéaire sur le nombre d'éléments dans le tas, où la construction d'un ensemble est O (N log N) sauf s'il est construit à partir d'une séquence qui est déjà ordonnée (dans ce cas il est également linéaire).]

Un ensemble vous permet un accès complet dans l'ordre trié, vous pouvez donc, par exemple, trouver deux éléments quelque part au milieu de l'ensemble, puis parcourir dans l'ordre de l'un à l'autre.

42
Jerry Coffin

std::priority_queue Permet de faire ce qui suit:

  1. Insère un élément O(log n)
  2. Récupère l'élément le plus petitO(1)
  3. Efface l'élément le plus petitO(log n)

tandis que std::set a plus de possibilités:

  1. Insérez n'importe quel élément O(log n) et la constante est supérieure à celle de std::priority_queue
  2. Trouver tout élément O(log n)
  3. Trouvez un élément,> = que celui que vous recherchez O(log n) (lower_bound)
  4. Effacer tout élément O(log n)
  5. Passer à l'élément précédent/suivant dans l'ordre trié O(1)
  6. Récupère l'élément le plus petitO(1)
  7. Récupère l'élément le plus grandO(1)
30
Ixanezis

set/multiset sont généralement soutenus par un arbre binaire. http://en.wikipedia.org/wiki/Binary_tree

priority_queue est généralement soutenu par un tas. http://en.wikipedia.org/wiki/Heap_ (data_structure)

La question est donc vraiment de savoir quand utiliser un arbre binaire au lieu d'un tas?

Les deux structures sont disposées dans un arbre, mais les règles concernant la relation entre les ancêtres sont différentes.

Nous appellerons les positions P pour parent, L pour enfant gauche et R pour enfant droit.

Dans un arbre binaire L <P <R.

Dans un tas P <L et P <R

Les arbres binaires trient donc "latéralement" et les tas trient "vers le haut".

Donc, si nous regardons cela comme un triangle, dans l'arbre binaire L, P, R sont complètement triés, alors que dans le tas, la relation entre L et R est inconnue (seule leur relation avec P).

Cela a les effets suivants:

  • Si vous avez un tableau non trié et que vous souhaitez le transformer en arbre binaire, cela prend O(nlogn) temps. Si vous voulez le transformer en tas, cela ne prend que O(n) temps, (car il compare juste pour trouver l'élément extrême)

  • Les tas sont plus efficaces si vous n'avez besoin que de l'élément extrême (le plus bas ou le plus élevé selon une fonction de comparaison). Les tas ne font que les comparaisons (paresseusement) nécessaires pour déterminer l'élément extrême.

  • Les arbres binaires effectuent les comparaisons nécessaires pour ordonner la collection entière et garder la collection entière triée tout le temps.

  • Les tas ont une recherche en temps constant (aperçu) de l'élément le plus bas, les arbres binaires ont une recherche en temps logarithmique de l'élément le plus bas.

23
Andrew Tomazos

Étant donné que _std::priority_queue_ et _std::set_ (et _std::multiset_) sont des conteneurs de données qui stockent des éléments et vous permettent d'y accéder de manière ordonnée, et ont la même complexité d'insertion O(log n) , quels sont les avantages d'utiliser l'un par rapport à l'autre (ou, quel genre de situations appellent l'un ou l'autre?)?

Même si insérer et effacer les opérations pour les deux conteneurs ont la même complexité O (log n), ces opérations pour _std::set_ sont plus lentes que pour _std::priority_queue_. En effet, _std::set_ effectue de nombreuses allocations de mémoire. Chaque élément de _std::set_ est stocké à sa propre allocation. _std::priority_queue_ (avec le conteneur _std::vector_ sous-jacent par défaut) utilise une allocation unique pour stocker tous les éléments. D'autre part, _std::priority_queue_ utilise de nombreuses opérations d'échange sur ses éléments tandis que _std::set_ utilise uniquement l'échange de pointeurs. Donc, si l'échange est une opération très lente pour le type d'élément, l'utilisation de _std::set_ peut être plus efficace. De plus, l'élément peut ne pas être échangeable du tout.

La surcharge de mémoire pour _std::set_ est beaucoup plus importante, car elle doit stocker de nombreux pointeurs entre ses nœuds.

6
anton_rh