Coda di priorità in .Net [chiuso]


216

Sto cercando un'implementazione .NET di una coda prioritaria o di una struttura di dati heap

Le code prioritarie sono strutture di dati che offrono maggiore flessibilità rispetto al semplice ordinamento, poiché consentono a nuovi elementi di entrare in un sistema a intervalli arbitrari. È molto più conveniente inserire un nuovo lavoro in una coda prioritaria piuttosto che riordinare tutto su tale arrivo.

La coda di priorità di base supporta tre operazioni principali:

  • Inserisci (Q, x). Dato un elemento x con chiave k, inserirlo nella coda di priorità Q.
  • Trova-minima (Q). Restituisce un puntatore all'elemento il cui valore chiave è inferiore a qualsiasi altra chiave nella coda di priorità Q.
  • Elimina-minima (Q). Rimuovere l'elemento dalla coda di priorità Q la cui chiave è minima

A meno che non stia guardando nel posto sbagliato, non ce n'è uno nel framework. Qualcuno è a conoscenza di una buona o dovrei farlo io?


34
Cordiali saluti, ho sviluppato una coda di priorità C # facile da usare e altamente ottimizzata, che può essere trovata qui . È stato sviluppato appositamente per le applicazioni di pathfinding (A *, ecc.), Ma dovrebbe funzionare perfettamente anche per qualsiasi altra applicazione.
Pubblicherei

1
ParallelExtensionsExtras ha un ConcurrentPriorityQueue code.msdn.microsoft.com/ParExtSamples
VoteCoffee

Introduci spudoratamente PriorityQueue , come parte dello sforzo di portare API simultanee Java su .net per Spring.Net. È sia un heap che una coda con supporto generico completo. Binario può essere scaricato qui .
Kenneth Xu,

@ BlueRaja-DannyPflughoeft Potresti aggiungere una risposta?
mafu,

1
Solo per riassumere. Non ci sono strutture di dati heap in .net, né in .net core ora. Sebbene Array.Sort gli utenti per grandi numeri. Esistono implementazioni interne .
Artyom,

Risposte:


44

Mi piace usare le classi OrderedBage OrderedSetin PowerCollections come code prioritarie.


60
OrderedBag / OrderedSet svolgono più lavoro del necessario, utilizzano un albero rosso-nero anziché un heap.
Dan Berindei,

3
@DanBerindei: non è necessario lavorare se è necessario eseguire il calcolo in esecuzione (eliminare gli elementi precedenti), l'heap supporta solo l'eliminazione di min o max
Svisstack

69

Potresti gradire IntervalHeap dalla libreria di raccolte generiche C5 . Per citare la guida per l' utente

La classe IntervalHeap<T>implementa l'interfaccia IPriorityQueue<T>usando un heap di intervallo memorizzato come una matrice di coppie. Le operazioni FindMin e FindMax e il get-accessor dell'indicizzatore richiedono tempo O (1). Le operazioni DeleteMin, DeleteMax, Add e Update e il set-accessor dell'indicizzatore richiedono tempo O (log n). Contrariamente a una normale coda di priorità, un heap di intervallo offre operazioni minime e massime con la stessa efficienza.

L'API è abbastanza semplice

> var heap = new C5.IntervalHeap<int>();
> heap.Add(10);
> heap.Add(5);
> heap.FindMin();
5

Installa da Nuget https://www.nuget.org/packages/C5 o GitHub https://github.com/sestoft/C5/


3
Questa sembra essere una libreria molto solida e viene fornita con 1400 test unitari.
ECC-Dan,

2
Ho provato ad usarlo ma ha gravi difetti. IntervalHeap non ha un concetto incorporato di priorità e ti obbliga a implementare IComparable o IComparer rendendolo una raccolta ordinata e non una "Priorità". Ancora peggio, non esiste un modo diretto per aggiornare la priorità di alcune voci precedenti !!!
morteza khosravi

52

Ecco il mio tentativo di un heap .NET

public abstract class Heap<T> : IEnumerable<T>
{
    private const int InitialCapacity = 0;
    private const int GrowFactor = 2;
    private const int MinGrow = 1;

    private int _capacity = InitialCapacity;
    private T[] _heap = new T[InitialCapacity];
    private int _tail = 0;

    public int Count { get { return _tail; } }
    public int Capacity { get { return _capacity; } }

    protected Comparer<T> Comparer { get; private set; }
    protected abstract bool Dominates(T x, T y);

    protected Heap() : this(Comparer<T>.Default)
    {
    }

    protected Heap(Comparer<T> comparer) : this(Enumerable.Empty<T>(), comparer)
    {
    }

    protected Heap(IEnumerable<T> collection)
        : this(collection, Comparer<T>.Default)
    {
    }

    protected Heap(IEnumerable<T> collection, Comparer<T> comparer)
    {
        if (collection == null) throw new ArgumentNullException("collection");
        if (comparer == null) throw new ArgumentNullException("comparer");

        Comparer = comparer;

        foreach (var item in collection)
        {
            if (Count == Capacity)
                Grow();

            _heap[_tail++] = item;
        }

        for (int i = Parent(_tail - 1); i >= 0; i--)
            BubbleDown(i);
    }

    public void Add(T item)
    {
        if (Count == Capacity)
            Grow();

        _heap[_tail++] = item;
        BubbleUp(_tail - 1);
    }

    private void BubbleUp(int i)
    {
        if (i == 0 || Dominates(_heap[Parent(i)], _heap[i])) 
            return; //correct domination (or root)

        Swap(i, Parent(i));
        BubbleUp(Parent(i));
    }

    public T GetMin()
    {
        if (Count == 0) throw new InvalidOperationException("Heap is empty");
        return _heap[0];
    }

    public T ExtractDominating()
    {
        if (Count == 0) throw new InvalidOperationException("Heap is empty");
        T ret = _heap[0];
        _tail--;
        Swap(_tail, 0);
        BubbleDown(0);
        return ret;
    }

    private void BubbleDown(int i)
    {
        int dominatingNode = Dominating(i);
        if (dominatingNode == i) return;
        Swap(i, dominatingNode);
        BubbleDown(dominatingNode);
    }

    private int Dominating(int i)
    {
        int dominatingNode = i;
        dominatingNode = GetDominating(YoungChild(i), dominatingNode);
        dominatingNode = GetDominating(OldChild(i), dominatingNode);

        return dominatingNode;
    }

    private int GetDominating(int newNode, int dominatingNode)
    {
        if (newNode < _tail && !Dominates(_heap[dominatingNode], _heap[newNode]))
            return newNode;
        else
            return dominatingNode;
    }

    private void Swap(int i, int j)
    {
        T tmp = _heap[i];
        _heap[i] = _heap[j];
        _heap[j] = tmp;
    }

    private static int Parent(int i)
    {
        return (i + 1)/2 - 1;
    }

    private static int YoungChild(int i)
    {
        return (i + 1)*2 - 1;
    }

    private static int OldChild(int i)
    {
        return YoungChild(i) + 1;
    }

    private void Grow()
    {
        int newCapacity = _capacity*GrowFactor + MinGrow;
        var newHeap = new T[newCapacity];
        Array.Copy(_heap, newHeap, _capacity);
        _heap = newHeap;
        _capacity = newCapacity;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _heap.Take(Count).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public class MaxHeap<T> : Heap<T>
{
    public MaxHeap()
        : this(Comparer<T>.Default)
    {
    }

    public MaxHeap(Comparer<T> comparer)
        : base(comparer)
    {
    }

    public MaxHeap(IEnumerable<T> collection, Comparer<T> comparer)
        : base(collection, comparer)
    {
    }

    public MaxHeap(IEnumerable<T> collection) : base(collection)
    {
    }

    protected override bool Dominates(T x, T y)
    {
        return Comparer.Compare(x, y) >= 0;
    }
}

public class MinHeap<T> : Heap<T>
{
    public MinHeap()
        : this(Comparer<T>.Default)
    {
    }

    public MinHeap(Comparer<T> comparer)
        : base(comparer)
    {
    }

    public MinHeap(IEnumerable<T> collection) : base(collection)
    {
    }

    public MinHeap(IEnumerable<T> collection, Comparer<T> comparer)
        : base(collection, comparer)
    {
    }

    protected override bool Dominates(T x, T y)
    {
        return Comparer.Compare(x, y) <= 0;
    }
}

Alcuni test:

[TestClass]
public class HeapTests
{
    [TestMethod]
    public void TestHeapBySorting()
    {
        var minHeap = new MinHeap<int>(new[] {9, 8, 4, 1, 6, 2, 7, 4, 1, 2});
        AssertHeapSort(minHeap, minHeap.OrderBy(i => i).ToArray());

        minHeap = new MinHeap<int> { 7, 5, 1, 6, 3, 2, 4, 1, 2, 1, 3, 4, 7 };
        AssertHeapSort(minHeap, minHeap.OrderBy(i => i).ToArray());

        var maxHeap = new MaxHeap<int>(new[] {1, 5, 3, 2, 7, 56, 3, 1, 23, 5, 2, 1});
        AssertHeapSort(maxHeap, maxHeap.OrderBy(d => -d).ToArray());

        maxHeap = new MaxHeap<int> {2, 6, 1, 3, 56, 1, 4, 7, 8, 23, 4, 5, 7, 34, 1, 4};
        AssertHeapSort(maxHeap, maxHeap.OrderBy(d => -d).ToArray());
    }

    private static void AssertHeapSort(Heap<int> heap, IEnumerable<int> expected)
    {
        var sorted = new List<int>();
        while (heap.Count > 0)
            sorted.Add(heap.ExtractDominating());

        Assert.IsTrue(sorted.SequenceEqual(expected));
    }
}

2
Consiglierei di cancellare il valore di heap in ExtractDominating, in modo che non rimanga sull'oggetto referenziato più a lungo del necessario (potenziale perdita di memoria). Per i tipi di valore ovviamente non è un problema.
Wout

5
Bello ma non puoi rimuovere elementi da esso? Questa è un'operazione importante per le code prioritarie.
Tom Larkworthy,

Sembra che l'oggetto sottostante sia un array. Non sarebbe meglio come un albero binario?
Grunion Shaftoe,

1
@OhadSchneider molto, stavo solo cercando in heap min e ho provato a fare quello che hai fatto rendendolo generico e heap min o max! ottimo lavoro
Gilad,

1
@Gilad IEqualityComparer<T>non sarebbe abbastanza, in quanto ciò ti direbbe solo se due elementi sono uguali, mentre devi conoscere la relazione tra loro (chi è più piccolo / più grande). È vero che avrei potuto usare IComparer<T>però ...
Ohad Schneider,

23

eccone uno che ho appena scritto, forse non è così ottimizzato (usa solo un dizionario ordinato) ma semplice da capire. puoi inserire oggetti di diverso tipo, quindi nessuna coda generica.

using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;

namespace PrioQueue
{
    public class PrioQueue
    {
        int total_size;
        SortedDictionary<int, Queue> storage;

        public PrioQueue ()
        {
            this.storage = new SortedDictionary<int, Queue> ();
            this.total_size = 0;
        }

        public bool IsEmpty ()
        {
            return (total_size == 0);
        }

        public object Dequeue ()
        {
            if (IsEmpty ()) {
                throw new Exception ("Please check that priorityQueue is not empty before dequeing");
            } else
                foreach (Queue q in storage.Values) {
                    // we use a sorted dictionary
                    if (q.Count > 0) {
                        total_size--;
                        return q.Dequeue ();
                    }
                }

                Debug.Assert(false,"not supposed to reach here. problem with changing total_size");

                return null; // not supposed to reach here.
        }

        // same as above, except for peek.

        public object Peek ()
        {
            if (IsEmpty ())
                throw new Exception ("Please check that priorityQueue is not empty before peeking");
            else
                foreach (Queue q in storage.Values) {
                    if (q.Count > 0)
                        return q.Peek ();
                }

                Debug.Assert(false,"not supposed to reach here. problem with changing total_size");

                return null; // not supposed to reach here.
        }

        public object Dequeue (int prio)
        {
            total_size--;
            return storage[prio].Dequeue ();
        }

        public void Enqueue (object item, int prio)
        {
            if (!storage.ContainsKey (prio)) {
                storage.Add (prio, new Queue ());
              }
            storage[prio].Enqueue (item);
            total_size++;

        }
    }
}

questo non consente però più voci con la stessa priorità?
Letseatlunch

2
lo fa. quando invochi il metodo Enqueue, verrà aggiunto l'elemento alla coda di quella priorità. (la parte in altro nel metodo accodamento.)
Kobi7

5
Cosa intendi con "non è davvero una coda prioritaria nel significato dell'informatica"? Che ne dici di farti credere che questa non è una coda prioritaria?
Mark Byers,

14
-1 per non usare generici.
cdiggins

2
Uno dei maggiori vantaggi di Heap / PriorityQueue è la complessità O (1) dell'estrazione min / max, ovvero l'operazione Peek. E qui comporta la configurazione dell'enumeratore, il for-loop, ecc. Perché !? Inoltre, l'operazione "Accoda" anziché essere O (logN) - un'altra caratteristica chiave dell'heap, ha un colpo O (longN) a causa di "ContainsKey", un secondo (di nuovo O (longN)) per aggiungere la voce Queue (se necessario), un terzo per recuperare effettivamente la coda (la riga di archiviazione [prio]) e infine un'aggiunta lineare a quella coda. Questo è veramente folle alla luce dell'implementazione dell'algoritmo di base.
Jonan Georgiev,


9

Come menzionato in Microsoft Collections for .NET , Microsoft ha scritto (e condiviso online) 2 classi interne PriorityQueue all'interno di .NET Framework. Il loro codice è disponibile per provare.

EDIT: Come ha commentato @ mathusum-mut, c'è un bug in una delle classi interne PriorityQueue di Microsoft (la comunità SO ha, ovviamente, fornito correzioni per esso): Bug nella priorità interna Microsoft di PriorityQueue <T>?


10
Un bug è stato trovato in una delle implementazioni qui: stackoverflow.com/questions/44221454/...
MathuSum Mut

ohh! Vedo che tutte queste classi PriorityQueue<T>nell'origine condivisa di Microsoft sono contrassegnate con identificatore di internalaccesso. Quindi vengono utilizzati solo dalle funzionalità interne del framework. Non sono disponibili per il consumo generale semplicemente facendo riferimento windowsbase.dllin un progetto C #. L'unico modo è copiare l'origine condivisa nel progetto stesso all'interno di un file di classe.
RBT


7
class PriorityQueue<T>
{
    IComparer<T> comparer;
    T[] heap;
    public int Count { get; private set; }
    public PriorityQueue() : this(null) { }
    public PriorityQueue(int capacity) : this(capacity, null) { }
    public PriorityQueue(IComparer<T> comparer) : this(16, comparer) { }
    public PriorityQueue(int capacity, IComparer<T> comparer)
    {
        this.comparer = (comparer == null) ? Comparer<T>.Default : comparer;
        this.heap = new T[capacity];
    }
    public void push(T v)
    {
        if (Count >= heap.Length) Array.Resize(ref heap, Count * 2);
        heap[Count] = v;
        SiftUp(Count++);
    }
    public T pop()
    {
        var v = top();
        heap[0] = heap[--Count];
        if (Count > 0) SiftDown(0);
        return v;
    }
    public T top()
    {
        if (Count > 0) return heap[0];
        throw new InvalidOperationException("优先队列为空");
    }
    void SiftUp(int n)
    {
        var v = heap[n];
        for (var n2 = n / 2; n > 0 && comparer.Compare(v, heap[n2]) > 0; n = n2, n2 /= 2) heap[n] = heap[n2];
        heap[n] = v;
    }
    void SiftDown(int n)
    {
        var v = heap[n];
        for (var n2 = n * 2; n2 < Count; n = n2, n2 *= 2)
        {
            if (n2 + 1 < Count && comparer.Compare(heap[n2 + 1], heap[n2]) > 0) n2++;
            if (comparer.Compare(v, heap[n2]) >= 0) break;
            heap[n] = heap[n2];
        }
        heap[n] = v;
    }
}

facile.


13
A volte vedo cose del genere for (var n2 = n / 2; n > 0 && comparer.Compare(v, heap[n2]) > 0; n = n2, n2 /= 2) heap[n] = heap[n2]; e mi chiedo se valga la pena di essere allineate

1
@DustinBreakey personal style :)
Shimou Dong

3
ma sicuramente non leggibile da altri. Prendi in considerazione la possibilità di scrivere un codice che non lasci un punto interrogativo sospeso sulla testa dello sviluppatore.
alzaimar,

3

Utilizzare un traduttore da Java a C # sull'implementazione Java (java.util.PriorityQueue) nel framework Collezioni Java, oppure utilizzare in modo più intelligente l'algoritmo e il codice core e collegarlo a una classe C # personalizzata che aderisce al framework C # Collections API per le code, o almeno le raccolte.


Funziona, ma sfortunatamente IKVM non supporta i generici Java, quindi perdi la sicurezza dei tipi.
Lumaca meccanica

8
Non esiste "Java generics" quando si ha a che fare con il bytecode Java compilato. IKVM non può supportarlo.
Segna il

3

AlgoKit

Ho scritto una libreria open source chiamata AlgoKit , disponibile tramite NuGet . Contiene:

  • Cumuli impliciti di d-ary (ArrayHeap),
  • Cumuli binomiali ,
  • Associazione di heap .

Il codice è stato ampiamente testato. Consiglio vivamente di provarlo.

Esempio

var comparer = Comparer<int>.Default;
var heap = new PairingHeap<int, string>(comparer);

heap.Add(3, "your");
heap.Add(5, "of");
heap.Add(7, "disturbing.");
heap.Add(2, "find");
heap.Add(1, "I");
heap.Add(6, "faith");
heap.Add(4, "lack");

while (!heap.IsEmpty)
    Console.WriteLine(heap.Pop().Value);

Perché quei tre cumuli?

La scelta ottimale dell'implementazione dipende fortemente dall'input - come mostrano Larkin, Sen e Tarjan in Uno studio empirico di base sulle code prioritarie , arXiv: 1403.0252v1 [cs.DS] . Hanno testato cumuli impliciti di d-ary, cumuli di accoppiamento, cumuli di Fibonacci, cumuli binomiali, cumuli espliciti di d-ary, cumuli di accoppiamento di rango, cumuli di terremoto, cumuli di violazione, cumuli deboli di livello inferiore e cumuli rigorosi di Fibonacci.

AlgoKit presenta tre tipi di cumuli che sembrano essere i più efficienti tra quelli testati.

Suggerimento a scelta

Per un numero relativamente piccolo di elementi, potresti essere interessato a utilizzare i cumuli impliciti, in particolare i cumuli quaternari (impliciti 4-ary). Nel caso di operare su heap di dimensioni maggiori, le strutture ammortizzate come i cumuli binomiali e i cumuli di accoppiamento dovrebbero funzionare meglio.



1

Ho avuto lo stesso problema di recente e ho finito per creare un pacchetto NuGet per questo.

Questo implementa una coda di priorità standard basata su heap. Ha anche tutte le solite prelibatezze delle raccolte BCL: ICollection<T>e IReadOnlyCollection<T>implementazione, IComparer<T>supporto personalizzato , capacità di specificare una capacità iniziale e un modo DebuggerTypeProxyper rendere la raccolta più facile da lavorare nel debugger.

Esiste anche una versione Inline del pacchetto che installa solo un singolo file .cs nel progetto (utile se si desidera evitare di assumere dipendenze visibili esternamente).

Maggiori informazioni sono disponibili sulla pagina di github .


1

Una semplice implementazione Max Heap.

https://github.com/bharathkumarms/AlgorithmsMadeEasy/blob/master/AlgorithmsMadeEasy/MaxHeap.cs

using System;
using System.Collections.Generic;
using System.Linq;

namespace AlgorithmsMadeEasy
{
    class MaxHeap
    {
        private static int capacity = 10;
        private int size = 0;
        int[] items = new int[capacity];

        private int getLeftChildIndex(int parentIndex) { return 2 * parentIndex + 1; }
        private int getRightChildIndex(int parentIndex) { return 2 * parentIndex + 2; }
        private int getParentIndex(int childIndex) { return (childIndex - 1) / 2; }

        private int getLeftChild(int parentIndex) { return this.items[getLeftChildIndex(parentIndex)]; }
        private int getRightChild(int parentIndex) { return this.items[getRightChildIndex(parentIndex)]; }
        private int getParent(int childIndex) { return this.items[getParentIndex(childIndex)]; }

        private bool hasLeftChild(int parentIndex) { return getLeftChildIndex(parentIndex) < size; }
        private bool hasRightChild(int parentIndex) { return getRightChildIndex(parentIndex) < size; }
        private bool hasParent(int childIndex) { return getLeftChildIndex(childIndex) > 0; }

        private void swap(int indexOne, int indexTwo)
        {
            int temp = this.items[indexOne];
            this.items[indexOne] = this.items[indexTwo];
            this.items[indexTwo] = temp;
        }

        private void hasEnoughCapacity()
        {
            if (this.size == capacity)
            {
                Array.Resize(ref this.items,capacity*2);
                capacity *= 2;
            }
        }

        public void Add(int item)
        {
            this.hasEnoughCapacity();
            this.items[size] = item;
            this.size++;
            heapifyUp();
        }

        public int Remove()
        {
            int item = this.items[0];
            this.items[0] = this.items[size-1];
            this.items[this.size - 1] = 0;
            size--;
            heapifyDown();
            return item;
        }

        private void heapifyUp()
        {
            int index = this.size - 1;
            while (hasParent(index) && this.items[index] > getParent(index))
            {
                swap(index, getParentIndex(index));
                index = getParentIndex(index);
            }
        }

        private void heapifyDown()
        {
            int index = 0;
            while (hasLeftChild(index))
            {
                int bigChildIndex = getLeftChildIndex(index);
                if (hasRightChild(index) && getLeftChild(index) < getRightChild(index))
                {
                    bigChildIndex = getRightChildIndex(index);
                }

                if (this.items[bigChildIndex] < this.items[index])
                {
                    break;
                }
                else
                {
                    swap(bigChildIndex,index);
                    index = bigChildIndex;
                }
            }
        }
    }
}

/*
Calling Code:
    MaxHeap mh = new MaxHeap();
    mh.Add(10);
    mh.Add(5);
    mh.Add(2);
    mh.Add(1);
    mh.Add(50);
    int maxVal  = mh.Remove();
    int newMaxVal = mh.Remove();
*/

-3

La seguente implementazione di un PriorityQueueusi SortedSetdalla libreria di sistema.

using System;
using System.Collections.Generic;

namespace CDiggins
{
    interface IPriorityQueue<T, K> where K : IComparable<K>
    {
        bool Empty { get; }
        void Enqueue(T x, K key);
        void Dequeue();
        T Top { get; }
    }

    class PriorityQueue<T, K> : IPriorityQueue<T, K> where K : IComparable<K>
    {
        SortedSet<Tuple<T, K>> set;

        class Comparer : IComparer<Tuple<T, K>> {
            public int Compare(Tuple<T, K> x, Tuple<T, K> y) {
                return x.Item2.CompareTo(y.Item2);
            }
        }

        PriorityQueue() { set = new SortedSet<Tuple<T, K>>(new Comparer()); }
        public bool Empty { get { return set.Count == 0;  } }
        public void Enqueue(T x, K key) { set.Add(Tuple.Create(x, key)); }
        public void Dequeue() { set.Remove(set.Max); }
        public T Top { get { return set.Max.Item1; } }
    }
}

6
SortedSet.Add fallirà (e restituirà false) se hai già un articolo nel set con la stessa "priorità" dell'articolo che stai tentando di aggiungere. Quindi ... se A.Compare (B) == 0 e A sono già nell'elenco, la tua funzione PriorityQueue.Enqueue fallirà silenziosamente.
Joseph

Ti dispiace spiegare cosa sono T xe K key? Immagino che questo sia un trucco per consentire la duplicazione T xe devo generare una chiave univoca (ad es. UUID)?
Thariq Nugrohotomo,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.