web-dev-qa-db-fra.com

Efficacité de la file d'attente prioritaire STL

J'ai une application (C++) qui je pense serait bien servie par une STL priority_queue. La documentation dit:

Priority_queue est un adaptateur de conteneur, ce qui signifie qu'il est implémenté au-dessus d'un type de conteneur sous-jacent. Par défaut, ce type sous-jacent est un vecteur, mais un type différent peut être sélectionné explicitement.

et

Les files d'attente prioritaires sont un concept standard et peuvent être implémentées de différentes manières; cette implémentation utilise des tas.

J'avais précédemment supposé que top() est O(1), et que Push() serait un O(logn) (les deux raisons pour lesquelles je a choisi le priority_queue en premier lieu) - mais la documentation ne confirme ni ne nie cette hypothèse.

En creusant plus profondément, les documents du concept Sequence disent:

La complexité de l'insertion et de l'effacement d'un seul élément dépend de la séquence.

Le priority_queue Utilise un vector (par défaut) comme tas, ce qui:

... prend en charge l'accès aléatoire aux éléments, l'insertion et la suppression d'éléments à temps constant à la fin, et l'insertion et la suppression linéaires d'éléments au début ou au milieu.

Je déduis que, en utilisant le priority_queue Par défaut, top() est O(1) et Push() est O(n).

Question 1: Est-ce correct? (top() l'accès est O(1) et Push() est O(n)?)

Question 2: Pourrais-je atteindre l'efficacité O(logn) sur Push() si j'utilisais un set (ou multiset) au lieu d'un vector pour l'implémentation du priority_queue? Quelles seraient les conséquences de faire cela? Quelles autres opérations en souffriraient en conséquence?

N.B .: Je m'inquiète de l'efficacité du temps ici, pas de l'espace.

36
Chris Tonkinson

L'adaptateur de file d'attente prioritaire utilise les algorithmes de tas de bibliothèque standard pour créer et accéder à la file d'attente - c'est la complexité de ces algorithmes que vous devriez rechercher dans la documentation.

L'opération top () est évidemment O(1) mais vraisemblablement vous voulez pop () le tas après l'avoir appelé qui (selon Josuttis ) est O (2 * log (N)) et Push () est O(log(N)) - même source.

Et à partir de la norme C++, 25.6.3.1, Push_heap:

Complexité: tout au plus comparaisons log (dernier - premier).

et pop_heap:

Complexité: au plus 2 * log (dernières - premières) comparaisons.

33
anon

top() - O(1) - car il retourne juste l'élément @ front.

Push() -

  • insérer dans le vecteur - 0(1) amorti
  • Push_into_heap - Au plus, des comparaisons log (n). O (connecté)

    donc la complexité de Push () est - log (n)

7
aJ.

Non, ce n'est pas correct. top () est O(1) et Push () est O (log n). Lisez la note 2 de la documentation pour voir que cet adaptateur ne permet pas d'itérer à travers le vecteur. Neil est correct à propos de pop (), mais cela permet toujours de travailler avec le tas en faisant des insertions et des suppressions en temps O (log n).

5
Yuval F

Si la structure de données sous-jacente est un tas, top () sera à temps constant et Push (EDIT: et pop) sera logarithmique (comme vous le dites). Le vecteur est juste utilisé pour stocker ces choses comme ceci:

TAS:
1
2 3
8 12 11 9

VECTEUR (utilisé pour stocker)

1 2 3 8 12 11 9

Vous pouvez le considérer comme l'élément à la position des enfants de i est (2i) et (2i + 1)

Ils utilisent le vecteur car il stocke les données de manière séquentielle (ce qui est beaucoup plus efficace et compatible avec le cache que discret)

Quelle que soit la façon dont il est stocké, un tas doit toujours être implémenté (en particulier par les dieux qui ont créé la bibliothèque STD) afin que la pop soit constante et que Push soit logarithmique.

2
Bob Fincheimer

La structure de données sous-jacente Priority_queue C++ STL est la structure de données Heap (Heap est un ADT non linéaire qui, basé sur un arbre binaire complet et un arbre binaire complet, est implémenté via un conteneur vectoriel (ou Array).

ex  Input data : 5 9 3 10 12 4.

Heap (Considering Min heap) would be :

                   [3]
             [9]             [4]
         [10]    [12]     [5]


   NOW , we store this min heap in to vector,             
      [3][9][4][10][12][5].
      Using formula ,
      Parent : ceiling of n-1/2
      Left Child : 2n+1
      Right Child : 2n+2 .
  Hence ,
    Time Complexity for 
             Top = O(1) , get element from root node.
             POP()= O(logn) , During deletion of root node ,there  is      chance to violation of  heap order . hence restructure of heap order takes at most O(logn) time (an element might move down to height of tree).
            Push()= O(logn) , During insertion also , there might chance to violation of  heap order . hence restructure of heap order takes at most O(logn) time (an element might move up to root from leaf node).
2
YADAV

Q1: Je n'ai pas regardé dans la norme, mais AFAIK, en utilisant vector (ou deque btw), la complexité serait O(1) pour top(), O(log n) pour Push() et pop(). J'ai comparé une fois std::priority_queue Avec mon propre tas avec O(1)Push() et top() et O(log n)pop() et ce n'était certainement pas aussi lent que O(n).

Q2: set n'est pas utilisable comme conteneur sous-jacent pour priority_queue (Pas une séquence), mais il serait possible d'utiliser set pour implémenter une file d'attente prioritaire avec O(log n)Push() et pop(). Cependant, cela ne surpasserait probablement pas l'implémentation de std::priority_queue Sur std::vector.

1
jpalecek