Facile d'utilisation min de file d'attente de priorité avec la mise à jour de clé en C++
Parfois lors de la programmation, concours, etc., nous avons besoin d'un simple travail de mise en œuvre de min de file d'attente de priorité à la baisse des clés pour mettre en œuvre Dijkstra algorithme etc.. j'ai souvent l'utilisation de set< paire<key_value, ID> >, et un tableau de correspondance d'ID-->key_value) ensemble pour y parvenir.
-
L'ajout d'un élément à l'ensemble prend O(log(N)) de temps. Pour construire une file d'attente prioritaire de N éléments, il nous suffit de les ajouter un par un dans le jeu. Cela prend un temps O(N log(N)) de temps au total.
-
L'élément avec min key_value est tout simplement le premier élément de l'ensemble. Sondage le plus petit élément prend O(1) fois. Retrait il prend O(log(N)) de temps.
-
Pour tester si certaines ID=k est, dans l'ensemble, nous avons tout d'abord chercher son key_value=v_k dans le tableau, puis la recherche de l'élément (v_k, k) dans le jeu. Cela prend O(log(N)) de temps.
-
Pour changer le key_value de certaines ID=k à partir de v_k à v_k', nous avons tout d'abord chercher son key_value=v_k dans le tableau, puis la recherche de l'élément (v_k, k) dans le jeu. Ensuite, nous supprimer cet élément de l'ensemble, puis insérez l'élément (v_k', k) dans le jeu. Nous avons ensuite mettre à jour le tableau, trop. Cela prend O(log(N)) de temps.
Bien que l'approche ci-dessus fonctionne, la plupart des manuels recommandent généralement à l'aide de binaire des tas de mettre en œuvre des files d'attente de priorité, comme le temps de la construction du tas binaire est seulement O(N). J'ai entendu dire qu'il y a un construit-dans la file d'attente de priorité structure de données dans la STL du C++ qui utilise les binaires des tas. Cependant, je ne suis pas sûr de la façon de mettre à jour le key_value pour que la structure de données.
Ce qui est le plus simple et le plus efficace de l'aide de min file d'attente de priorité avec la mise à jour de clé en C++?
- Que diriez - Boost.Tas?
Vous devez vous connecter pour publier un commentaire.
Bien, comme Darren déjà dit,
std::priority_queue
n'en avons pas les moyens pour diminuer la priorité d'un élément, et ni la suppression d'un élément autre que le courant min. Mais le défautstd::priority_queue
n'est rien de plus qu'un simple conteneur de l'adaptateur autour d'unstd::vector
qui utilise le std tas de fonctions de<algorithm>
(std::push_heap
,std::pop_heap
etstd::make_heap
). Donc, pour Dijkstra (où vous avez besoin de la priorité de mise à jour) j'ai l'habitude de faire ça moi-même et d'utiliser un simplestd::vector
.Un push est alors en O(log N) opérations de
De cours pour la construction d'une file d'attente à nouveau à partir de N éléments, nous ne poussent pas tous en utilisant cette O(log N) opération (ce qui rend la chose entière O(Nlog N)) mais juste de les mettre tous dans le vecteur suivie par une simple O(N)
Min élément est un simple O(1)
Une pop qui est la simple O(log N) séquence
Jusqu'à présent c'est tout ce que
std::priority_queue
fait habituellement sous le capot. Maintenant, pour modifier un élément prioritaire de la nous avons juste besoin de le changer (mais il peut être incorporé dans le type de l'élément) et de faire la séquence valide d'un tas de nouveauJe sais que c'est un O(N) opérations, mais d'un autre côté cela supprime toute nécessité de garder une trace de la position d'un enregistrement dans le tas avec une structure de données supplémentaires ou (pire encore) une augmentation des éléments de type. Et la dégradation des performances sur une échelle logarithmique de la mise à jour prioritaire est pas, en pratique, que des importants, compte tenu de la facilité d'utilisation, compact et linéaire de la mémoire useage de
std::vector
(qui a des répercussions de l'exécution, aussi), et le fait que j'ai souvent travailler avec des graphiques qui ont assez peu d'arêtes (linéaire en le nombre de sommets) de toute façon.Il ne peut pas dans tous les cas, le moyen le plus rapide, mais certainement le plus simple.
EDIT: Oh, et depuis la bibliothèque standard utilise max-tas, vous devez utiliser un équivalent de
>
pour comparer les priorités (cependant vous les obtenir à partir des éléments), au lieu de la valeur par défaut<
opérateur.O(N)
de recherche est fait pour trouver l'article dans le tas, l'élément est mis à jour et le tas est re-construit (àO(N)
de nouveau)?std::push_heap
surstd::make_heap
. La priorité est la partie de l'article, et non de la tas (bien sûr, cela nécessite un non-trivial comparaison prédicat). En utilisant simplementstd::make_heap
le tas n'a pas besoin de savoir qui de l'élément de priorité changé et nous pouvons le faire.Bien que ma réponse ne sera pas répondre à la question initiale, je pense qu'il pourrait être utile pour les personnes qui arrivent à cette question lors de la tentative de mettre en œuvre Dijkstra algorithme en C++/Java (comme moi), quelque chose qui a été commentaire par l'OP,
priority_queue
en C++ (ouPriorityQueue
en Java) ne fournissent pas undecrease-key
opération, comme dit précédemment. Une belle astuce pour l'utilisation de ces classes lors de la mise en œuvre de Dijkstra est à l'aide de "paresseux suppression". La boucle principale de l'algorithme de Dijkstra extraits du prochain nœud à être traitée à partir de la file d'attente de priorité, et analises tous ses nœuds adjacents, éventuellement en changeant le coût du chemin minimal pour un nœud dans la file d'attente de priorité. C'est le point oùdecrease-key
est généralement nécessaire afin de mettre à jour la valeur de ce noeud.Le truc, c'est pas changer à tous. Au lieu de cela, une "copie" de ce nœud (avec son nouveau meilleur coût), il est ajouté à la file d'attente de priorité. Ayant un faible coût, que la nouvelle copie du nœud sera extrait avant de la copie originale dans la file d'attente, de sorte qu'il sera traité plus tôt.
Le problème avec cette "paresseux suppression" est que la deuxième copie du nœud, avec la plus mauvaise coût, sera finalement extrait de la file d'attente de priorité. Mais qui seront toujours arriver après la seconde est ajoutée copie, avec un meilleur coût, est en cours de traitement. Donc la première chose que le principal Dijkstra, la boucle doit faire lors de l'extraction du nœud suivant de la file d'attente de priorité est de vérifier si le nœud a déjà visité (et nous savons que le chemin le plus court déjà). C'est à ce moment précis, lorsque nous allons faire le "paresseux suppression de" et de l'élément doit être tout simplement ignoré.
Cette solution aura un coût, à la fois dans la mémoire et du temps, parce que la file d'attente de priorité est le stockage de morts "éléments" que nous n'avons pas retiré. Mais le coût réel sera assez faible, et la programmation de cette solution est, à mon humble avis, plus facile que toute autre solution qui tente de simuler le manque
decrease-key
opération.Je ne pense pas que le
std::priority_queue
classe permet une mise en œuvre efficace dedecrease-key
opérations de style.J'ai roulé ma propre tas binaire basée sur la structure de données qui prend en charge ce, bascially très similaire à ce que vous avez décrit pour la
std::set
en fonction de la priorité de la file d'attente que vous avez:value
qui stocke les éléments depair<value, ID>
et un tableau de cartesID -> heap_index
. Dans le tas de routinesheapify_up, heapify_down
etc il est nécessaire de s'assurer que la cartographie tableau est conservé en synchronisation avec le courant tas position des éléments. Cela ajoute un peu d'extraO(1)
frais généraux.O(N)
selon l'algorithme décrit ici.O(1)
.ID
est actuellement dans le tas, il suffit d'unO(1)
de recherche dans le tableau de mappage. Cela permet également deO(1)
jeter un oeil à l'élément correspondant à l'uneID
.Decrease-key
implique unO(1)
de recherche dans le mappage d'un tableau, suivi par unO(log(N))
mise à jour le tas viaheapify_up, heapify_down
.O(log(N))
comme le popping est une exitsing élément dans le tas.Donc asymptotiquement le moteur d'exécution est amélioré pour un peu de l'exploitation par rapport à la
std::set
base de données de la structure. Une autre importante avancée est que le binaire tas peuvent être mis en œuvre sur un tableau, tandis que les arbres binaires sont basées sur les nœuds des conteneurs. Le supplément de la localité des données des tas binaire se traduit généralement par l'amélioration de l'exécution.Quelques problèmes avec cette mise en œuvre sont:
ID
's.ID
's, en commençant à zéro (autrement l'espace de la complexité de la cartographie de la matrice de coups!).Vous pourriez potentiellement à surmonter ces problèmes, si vous avez maintenu un mappage de table de hachage, plutôt que d'une cartographie de réseau, mais avec un peu plus de gestion d'exécution. Pour mon utilisation, entier
ID
's ont toujours été assez.Espère que cette aide.
heapify
routines je ne pense pas que vous pouvez utiliser lestd::
routines, je pense que vous avez besoin d'écrire vous êtes propre. Si vous avez été vraiment à la recherche de "la plus rapide" file d'attente de priorité pour les algorithmes comme Dijkstra, etc il peut également être utile de vérifier certaines données liées à l'structures: Brodal files d'attente, les tas de Fibonacci, n-ary tas, 2-3 tas pour n'en nommer que quelques-unes. Certaines de ces structures de données sont très complexes, mais l'offre théorique des améliorations à la complexité asymptotique...lazy-delete
etdecrease-key
type de stratégies qui peuvent être utile je pense. Qui est mieux dépend vraiment de votre application particulière. Si vous travaillez avec de très rares graphiques,lazy-delete
peut bien travailler (comme la taille du segment de mémoire de ne pas exploser). Plus dense graphiques, d'autre part, sont une autre histoire. Imaginez que vous avez un graphique avec une moyenne de sommet de degré de quelques centaines de --lazy-delete
pourrait aboutir à un énorme espace et le temps blow-up ici! Dynamique des tas avec une bonnedecrease-key
opération plomb àO(n*log(n))
Dijkstra-algorithmes de type. Certains autres réponses ici sontO(n^2)
...