web-dev-qa-db-fra.com

Mise à jour de Java PriorityQueue lorsque ses éléments changent de priorité

J'essaie d'utiliser une PriorityQueue pour ordonner des objets à l'aide d'une Comparator.

Cela peut être facilement réalisé, mais les variables de classe d'objets (avec lesquelles le comparateur calcule la priorité) peuvent changer après l'insertion initiale. La plupart des gens ont suggéré la solution simple qui consiste à supprimer l'objet, à mettre à jour les valeurs et à le réinsérer, comme c'est le cas lorsque le comparateur de la file d'attente prioritaire est activé.

Existe-t-il un meilleur moyen que de créer une classe wrapper autour de PriorityQueue pour le faire?

49
Marcus Whybrow

Vous devez supprimer et réinsérer, car la file d'attente fonctionne en plaçant les nouveaux éléments à la position appropriée lorsqu'ils sont insérés. Ceci est beaucoup plus rapide que l'alternative consistant à rechercher l'élément de priorité la plus élevée chaque fois que vous vous retirez de la file d'attente. L'inconvénient est que vous ne pouvez pas modifier la priorité une fois l'élément inséré. Un TreeMap a la même limitation (tout comme un HashMap, qui se brise également lorsque le hashcode de ses éléments change après l'insertion).

Si vous souhaitez écrire un wrapper, vous pouvez déplacer le code de comparaison de la file d'attente à la file d'attente. Vous n'auriez plus besoin de trier au moment de la mise en file d'attente (car l'ordre qu'il crée ne serait de toute façon pas fiable si vous autorisez les modifications).

Toutefois, les performances seront pires et vous souhaiterez vous synchroniser dans la file d'attente si vous modifiez l'une des priorités. Étant donné que vous devez ajouter un code de synchronisation lors de la mise à jour des priorités, vous pouvez également simplement retirer de la file d'attente et mettre en file d'attente (vous avez besoin de la référence à la file d'attente dans les deux cas).

33
Thilo

Je ne sais pas s'il existe une implémentation Java, mais si vous modifiez beaucoup les valeurs de clé, vous pouvez utiliser un segment de mémoire Fibonnaci, dont le coût amorti est O(1)/ diminue une valeur de clé d'une entrée dans le tas, plutôt que O(log(n)) comme dans un tas ordinaire.

10
Jon McCaffrey

Cela dépend beaucoup de savoir si vous avez le contrôle direct de quand les valeurs changent.

Si vous savez quand les valeurs changent, vous pouvez soit supprimer, soit réinsérer (ce qui est en fait assez coûteux, car sa suppression nécessite un balayage linéaire sur le tas!) .. De plus, vous pouvez utiliser une structure UpdatableHeap (non disponible en stock). bien que) pour cette situation. Il s’agit essentiellement d’un segment qui suit la position des éléments dans une table de hachage. Ainsi, lorsque la priorité d'un élément change, il peut réparer le tas. Troisièmement, vous pouvez rechercher un tas de Fibonacci qui fait la même chose.

Selon votre fréquence de mise à jour, une analyse linéaire/tri rapide/sélection rapide à chaque fois peut également fonctionner. En particulier si vous avez beaucoup plus de mises à jour que pulls, c'est la voie à suivre. QuickSelect est probablement préférable si vous avez des lots de mises à jour, puis des lots d'opérations pull.

5
Anony-Mousse

Pour déclencher reheapify, essayez ceci:

if(!priorityQueue.isEmpty()) {
    priorityQueue.add(priorityQueue.remove());
}
3
Techflash

Quelque chose que j’ai essayé et qui fonctionne jusqu’à présent, c’est jeter un coup d’œil furtif pour voir si la référence à l’objet que vous modifiez est identique à l’en-tête de PriorityQueue. Si c’est le cas, alors vous interrogez (), changez puis réinsérez ; sinon, vous pouvez changer sans interrogation, car lorsque la tête est interrogée, le tas est de toute façon hypertrophié.

DOWNSIDE: Ceci change la priorité pour les objets avec la même priorité. 

1
Phoenix King

Une solution simple que vous pouvez implémenter consiste à simplement rajouter cet élément dans la file d'attente prioritaire. Cela ne changera pas la façon dont vous extrayez les éléments bien qu'il consomme plus d'espace, mais cela ne sera pas trop pour affecter votre temps d'exécution.

Pour le prouver, considérons l'algorithme de dijkstra ci-dessous

public int[] dijkstra() {
int distance[] = new int[this.vertices];
int previous[] = new int[this.vertices];
for (int i = 0; i < this.vertices; i++) {
    distance[i] = Integer.MAX_VALUE;
    previous[i] = -1;
}
distance[0] = 0;
previous[0] = 0;
PriorityQueue<Node> pQueue = new PriorityQueue<>(this.vertices, new NodeComparison());
addValues(pQueue, distance);
while (!pQueue.isEmpty()) {
    Node n = pQueue.remove();
    List<Edge> neighbours = adjacencyList.get(n.position);
    for (Edge neighbour : neighbours) {
        if (distance[neighbour.destination] > distance[n.position] + neighbour.weight) {
            distance[neighbour.destination] = distance[n.position] + neighbour.weight;
            previous[neighbour.destination] = n.position;
            pQueue.add(new Node(neighbour.destination, distance[neighbour.destination]));
        }
    }
}
return previous;

}

Ici, notre intérêt est en ligne pQueue.add(new Node(neighbour.destination, distance[neighbour.destination])); Je ne change pas la priorité du nœud particulier en le supprimant et en ajoutant à nouveau. J'ajoute plutôt un nouveau nœud avec la même valeur mais une priorité différente . Maintenant. au moment de l'extraction, j'obtiendrai toujours ce nœud en premier parce que j'ai implémenté min heap ici et que le nœud avec une valeur supérieure à celle-ci (moins prioritaire) sera toujours extrait après et de cette manière, tous les nœuds voisins seront déjà relâchés dès le début sera extrait.

0
Harshit Agrawal