web-dev-qa-db-fra.com

Comment mettre à jour des éléments dans un tas? Fichier d'attente de priorité

Lors de l'utilisation d'un algorithme min/max-heap, les priorités peuvent changer. Une façon de gérer cela consiste à supprimer et à insérer l'élément pour mettre à jour l'ordre de la file d'attente.

Pour les files d'attente prioritaires implémentées à l'aide de tableaux, il peut s'agir d'un goulot d'étranglement des performances qui semble évitable, en particulier dans les cas où le changement de priorité est faible.

Même s'il ne s'agit pas d'une opération standard pour une file d'attente prioritaire , il s'agit d'une implémentation personnalisée qui peut être modifiée pour répondre à mes besoins.

Existe-t-il des méthodes bien connues de mise à jour des éléments dans le tas min/max?


Information de fond: Je ne suis pas un expert des arbres binaires, j'ai hérité d'un code qui présente un goulet d'étranglement des performances lors de la réinsertion d'éléments dans une file d'attente prioritaire. J'ai créé une fonction de réinsertion pour le min-tas qui réordonne le nouvel élément - ce qui donne une amélioration mesurable par rapport à (supprimer et insérer), mais cela semble le genre de problème que d'autres ont peut-être résolu de manière plus élégante. façon.

Je pourrais créer un lien vers le code si cela vous aide, mais je préférerais ne pas trop insister sur les détails de la mise en œuvre, car ce Q & A peut probablement rester général.

7
ideasman42

Solution typique

La solution habituelle consiste à marquer un élément comme non valide et à insérer un nouvel élément, puis à éliminer les entrées non valides au fur et à mesure de leur éjection.

Solution alternative

Si cette approche ne suffit pas, il est possible de restaurer l'invariant min-tas en étapes O (log n) tant que l'emplacement de la valeur en cours de modification est connu .

Rappelez-vous que min-heaps sont construits et maintenus en utilisant deux primitives, "siftup" et "siftdown" (bien que diverses sources aient des opinions divergentes sur ce qui est actif ou non). L'une de ces valeurs pousse les valeurs dans l'arbre et l'autre les fait flotter vers le haut.

Cas 1: la valeur est augmentée

Si la nouvelle valeur x1 est supérieure à l'ancienne valeur x0, seul l'arborescence située sous x doit être corrigée car parent(x) <= x0 < x1. Juste Poussez x vers le bas de l’arbre en échangeant x avec le plus petit de ses deux enfants, alors que x est plus gros que l’un de ses enfants .

Cas 2: la valeur est diminuée

Si la nouvelle valeur x1 est inférieure à l'ancienne valeur x, l'arborescence située sous x ne nécessite aucun ajustement, car x1 < x0 <= either_child(x). Au lieu de cela, nous avons juste besoin de nous déplacer vers le haut, en échangeant x avec son parent alors que x est inférieur à son parent . Les nœuds frères ne doivent pas nécessairement être pris en compte car ils sont déjà supérieurs ou égaux à un parent qui sera potentiellement remplacé par une valeur inférieure.

Cas 3: La valeur est inchangée

Aucun travail n'est nécessaire. Les invariants existants sont inchangés.

Code de travail en Python

Testez 1 000 000 d'essais: créez un tas aléatoire. Modifier une valeur choisie au hasard. Restaurez la condition de tas. Vérifiez que le résultat est un tas min.

from heapq import _siftup, _siftdown, heapify
from random import random, randrange, choice

def is_minheap(arr):
    return all(arr[i] >= arr[(i-1)//2] for i in range(1, len(arr)))

n = 40
trials = 1_000_000
for _ in range(trials):

    # Create a random heap
    data = [random() for i in range(n)]
    heapify(data)

    # Randomly alter a heap element
    i = randrange(n)
    x0 = data[i]
    x1 = data[i] = choice(data)

    # Restore the heap
    if x1 > x0:                 # value is increased
        _siftup(data, i)
    Elif x1 < x0:               # value is decreased
        _siftdown(data, 0, i)

    # Verify the results
    assert is_minheap(data), direction
14
Raymond Hettinger

Poster une réponse à sa propre question car elle inclut des liens vers du code de travail.


C'est en fait assez simple.

En règle générale, une implémentation min-tas comporte des fonctions de commande, voir exemple: BubbleUp/Down .

Ces fonctions peuvent s'exécuter sur l'élément modifié, en fonction de la modification par rapport à la valeur actuelle. par exemple:

if new_value < old_value {
    heap_bubble_up(heap, node);
} else if new_value > old_value {
    heap_bubble_down(heap, node);
}

Bien que le nombre d'opérations dépend de la distribution des valeurs, le nombre d'étapes est égal ou inférieur à celui d'une liste triée.

En général, les petites modifications sont beaucoup plus efficaces qu'un remove/insert.

Voir travail code , et test , qui implémente un min-tas avec insertion/suppression/redéfinition des priorités, sans recherche initiale (l'appelant stocke une référence opaque).


Même réorganiser uniquement les éléments requis peut constituer de nombreuses opérations pour un segment de mémoire volumineux.

Si cela est trop inefficace, un minimum de mémoire peut ne pas convenir.

Un arbre binaire pourrait être meilleur (arbre rouge-noir par exemple), où le retrait et l’insertion s’améliorent mieux.

Cependant, je ne suis pas sûr d'une capacité de rb-trees à réorganiser sur place , comme le ferait un min-tas.

2
ideasman42