2009-12-21 39 views
5

Sto cercando una coda di priorità con un'interfaccia simile a questo:C# Coda con priorità

class PriorityQueue<T> 
{ 
    public void Enqueue(T item, int priority) 
    { 
    } 

    public T Dequeue() 
    { 
    } 
} 

Tutte le implementazioni che ho visto dal presupposto che è un itemIComparable, ma non mi piace questo approccio; Voglio specificare la priorità quando la spingo in coda.

Se non esiste un'implementazione già pronta, qual è il modo migliore per farlo? Quale struttura dati sottostante dovrei usare? Una specie di albero autoequilibrante o cosa? Una struttura C# .net standard sarebbe carina.

+0

Hai intenzione di chiamarlo da più thread? – sohum

+0

@sohum: No ... il mio programma * è * threaded, ma solo un thread avrà bisogno di accedervi. – mpen

+0

La ragione che viene immediatamente in mente per T che supporta IComparable è che se si inseriscono due elementi nella coda con priorità 2, è comunque necessario confrontare gli articoli e decidere quale ordine elaborare gli elementi prioritari due. . . Quindi alla fine hai bisogno che T sia comparabile. Quindi, come farlo con la tua interfaccia ... Bene, hai alcuni buoni suggerimenti qui sotto. –

risposta

11

Se si dispone di un'implementazione coda di priorità esistenti sulla base di IComparable, si può facilmente utilizzare che per costruire la struttura è necessario:

public class CustomPriorityQueue<T> // where T need NOT be IComparable 
{ 
    private class PriorityQueueItem : IComparable<PriorityQueueItem> 
    { 
    private readonly T _item; 
    private readonly int _priority: 

    // obvious constructor, CompareTo implementation and Item accessor 
    } 

    // the existing PQ implementation where the item *does* need to be IComparable 
    private readonly PriorityQueue<PriorityQueueItem> _inner = new PriorityQueue<PriorityQueueItem>(); 

    public void Enqueue(T item, int priority) 
    { 
    _inner.Enqueue(new PriorityQueueItem(item, priority)); 
    } 

    public T Dequeue() 
    { 
    return _inner.Dequeue().Item; 
    } 
} 
0

Sembra che tu possa stampare il tuo con una serie di code, una per ciascuna priorità. Dizionario e basta aggiungerlo a quello appropriato.

+2

Questo ha prestazioni orribili per il pop, come si ha quindi per trovare la prima coda non vuota. – Yuliy

+0

@Yuliy: All'inizio pensavo anche a questo, ma se saltiamo fuori dalle code quando diventano vuote, non è davvero un problema? – mpen

+0

Hai intenzione di pagare da qualche parte per gestire la priorità. Se si dispone di un elenco collegato, è necessario eseguirlo nell'inserzione per trovare la "fine" della priorità corrente. La gestione della priorità non è gratuita. –

6

È possibile aggiungere controlli di sicurezza e cosa no, ma qui è una semplice implementazione utilizzando SortedList:

class PriorityQueue<T> { 
    SortedList<Pair<int>, T> _list; 
    int count; 

    public PriorityQueue() { 
     _list = new SortedList<Pair<int>, T>(new PairComparer<int>()); 
    } 

    public void Enqueue(T item, int priority) { 
     _list.Add(new Pair<int>(priority, count), item); 
     count++; 
    } 

    public T Dequeue() { 
     T item = _list[_list.Keys[0]]; 
     _list.RemoveAt(0); 
     return item; 
    } 
} 

sto supponendo che i valori minori di priority corrispondono agli elementi di priorità superiore (questo è facile da modificare).

Se più thread accedono alla coda, è necessario aggiungere anche un meccanismo di blocco. È facile, ma fammi sapere se hai bisogno di una guida qui.

SortedList è implementato internamente come un albero binario.

L'implementazione di cui sopra richiede le seguenti classi di supporto. Questo indirizzo di Lasse V. Karlsen commenta che gli oggetti con la stessa priorità non possono essere aggiunti usando l'implementazione naive usando uno SortedList.

class Pair<T> { 
    public T First { get; private set; } 
    public T Second { get; private set; } 

    public Pair(T first, T second) { 
     First = first; 
     Second = second; 
    } 

    public override int GetHashCode() { 
     return First.GetHashCode()^Second.GetHashCode(); 
    } 

    public override bool Equals(object other) { 
     Pair<T> pair = other as Pair<T>; 
     if (pair == null) { 
      return false; 
     } 
     return (this.First.Equals(pair.First) && this.Second.Equals(pair.Second)); 
    } 
} 

class PairComparer<T> : IComparer<Pair<T>> where T : IComparable { 
    public int Compare(Pair<T> x, Pair<T> y) { 
     if (x.First.CompareTo(y.First) < 0) { 
      return -1; 
     } 
     else if (x.First.CompareTo(y.First) > 0) { 
      return 1; 
     } 
     else { 
      return x.Second.CompareTo(y.Second); 
     } 
    } 
} 
+3

Il problema con SortedList è che non consente priorità duplicate, quindi ti obbliga a garantire priorità univoche. –

+1

Mi sembra più sensato che i numeri * più alti * corrispondano a una priorità * più alta *. – mpen

+1

Facilmente risolto, basta annullare i valori di priorità, se SortedList è sufficiente. –

5

Si potrebbe scrivere un wrapper per una delle implementazioni esistenti che modifica l'interfaccia in base alle tue preferenze:

using System; 

class PriorityQueueThatYouDontLike<T> where T: IComparable<T> 
{ 
    public void Enqueue(T item) { throw new NotImplementedException(); } 
    public T Dequeue() { throw new NotImplementedException(); } 
} 

class PriorityQueue<T> 
{ 
    class ItemWithPriority : IComparable<ItemWithPriority> 
    { 
     public ItemWithPriority(T t, int priority) 
     { 
      Item = t; 
      Priority = priority; 
     } 

     public T Item {get; private set;} 
     public int Priority {get; private set;} 

     public int CompareTo(ItemWithPriority other) 
     { 
      return Priority.CompareTo(other.Priority); 
     } 
    } 

    PriorityQueueThatYouDontLike<ItemWithPriority> q = new PriorityQueueThatYouDontLike<ItemWithPriority>(); 

    public void Enqueue(T item, int priority) 
    { 
     q.Enqueue(new ItemWithPriority(item, priority)); 
    } 

    public T Dequeue() 
    { 
     return q.Dequeue().Item; 
    } 
} 

Questo è lo stesso del suggerimento di itowlson. Ho impiegato più tempo a scrivere il mio perché ho compilato più metodi. : -s

+0

Sto ancora cercando di trovare una buona implementazione di 'PriorityQueueThatYouDontLike'.Anche quelli che usano IComparables non sembrano molto belli. – mpen

+0

Un heap funzionerebbe? –

+0

Ho un heap generico qui: http://vkarlsen.serveftp.com:81/websvn/listing.php?repname=LVK&path=/LVK_3_5/trunk/LVK.Core/Collections/, username e password entrambi 'guest' (senza le virgolette). L'heap cade preda del "problema" che hai menzionato nel richiedere IComparable, ma puoi usare la classe ItemWithPriority che Mark ha pubblicato. –

4

Ecco un'implementazione molto semplice e leggera con prestazioni O (log (n)) per push e pop. Utilizza un heap data structure costruito in cima a un elenco <T>.

/// <summary>Implements a priority queue of T, where T has an ordering.</summary> 
/// Elements may be added to the queue in any order, but when we pull 
/// elements out of the queue, they will be returned in 'ascending' order. 
/// Adding new elements into the queue may be done at any time, so this is 
/// useful to implement a dynamically growing and shrinking queue. Both adding 
/// an element and removing the first element are log(N) operations. 
/// 
/// The queue is implemented using a priority-heap data structure. For more 
/// details on this elegant and simple data structure see "Programming Pearls" 
/// in our library. The tree is implemented atop a list, where 2N and 2N+1 are 
/// the child nodes of node N. The tree is balanced and left-aligned so there 
/// are no 'holes' in this list. 
/// <typeparam name="T">Type T, should implement IComparable[T];</typeparam> 
public class PriorityQueue<T> where T : IComparable<T> { 
    /// <summary>Clear all the elements from the priority queue</summary> 
    public void Clear() { 
     mA.Clear(); 
    } 

    /// <summary>Add an element to the priority queue - O(log(n)) time operation.</summary> 
    /// <param name="item">The item to be added to the queue</param> 
    public void Add (T item) { 
     // We add the item to the end of the list (at the bottom of the 
     // tree). Then, the heap-property could be violated between this element 
     // and it's parent. If this is the case, we swap this element with the 
     // parent (a safe operation to do since the element is known to be less 
     // than it's parent). Now the element move one level up the tree. We repeat 
     // this test with the element and it's new parent. The element, if lesser 
     // than everybody else in the tree will eventually bubble all the way up 
     // to the root of the tree (or the head of the list). It is easy to see 
     // this will take log(N) time, since we are working with a balanced binary 
     // tree. 
     int n = mA.Count; mA.Add (item); 
     while (n != 0) { 
     int p = n/2; // This is the 'parent' of this item 
     if (mA[n].CompareTo (mA[p]) >= 0) break; // Item >= parent 
     T tmp = mA[n]; mA[n] = mA[p]; mA[p] = tmp; // Swap item and parent 
     n = p;   // And continue 
     } 
    } 

    /// <summary>Returns the number of elements in the queue.</summary> 
    public int Count { 
     get { return mA.Count; } 
    } 

    /// <summary>Returns true if the queue is empty.</summary> 
    /// Trying to call Peek() or Next() on an empty queue will throw an exception. 
    /// Check using Empty first before calling these methods. 
    public bool Empty { 
     get { return mA.Count == 0; } 
    } 

    /// <summary>Allows you to look at the first element waiting in the queue, without removing it.</summary> 
    /// This element will be the one that will be returned if you subsequently call Next(). 
    public T Peek() { 
     return mA[0]; 
    } 

    /// <summary>Removes and returns the first element from the queue (least element)</summary> 
    /// <returns>The first element in the queue, in ascending order.</returns> 
    public T Next() { 
     // The element to return is of course the first element in the array, 
     // or the root of the tree. However, this will leave a 'hole' there. We 
     // fill up this hole with the last element from the array. This will 
     // break the heap property. So we bubble the element downwards by swapping 
     // it with it's lower child until it reaches it's correct level. The lower 
     // child (one of the orignal elements with index 1 or 2) will now be at the 
     // head of the queue (root of the tree). 
     T val = mA[0]; 
     int nMax = mA.Count - 1; 
     mA[0] = mA[nMax]; mA.RemoveAt (nMax); // Move the last element to the top 

     int p = 0; 
     while (true) { 
     // c is the child we want to swap with. If there 
     // is no child at all, then the heap is balanced 
     int c = p * 2; if (c >= nMax) break; 

     // If the second child is smaller than the first, that's the one 
     // we want to swap with this parent. 
     if (c + 1 < nMax && mA[c + 1].CompareTo (mA[c]) < 0) c++; 
     // If the parent is already smaller than this smaller child, then 
     // we are done 
     if (mA[p].CompareTo (mA[c]) <= 0) break; 

     // Othewise, swap parent and child, and follow down the parent 
     T tmp = mA[p]; mA[p] = mA[c]; mA[c] = tmp; 
     p = c; 
     } 
     return val; 
    } 

    /// <summary>The List we use for implementation.</summary> 
    List<T> mA = new List<T>(); 
} 
+0

Ha detto che non voleva che 'T' fosse" IComparable ". – jason

+1

Ha scritto "anche quelli che usano IComparable non sembrano molto belli" e stavo rispondendo a questo. Il punto chiave è che una coda di priorità non richiede una lista completamente ordinata; tutto quello che dobbiamo sapere è quale è il primo elemento nell'elenco in qualsiasi momento. La struttura dei dati di heap che sto utilizzando non è completamente ordinata, ma mantiene un numero sufficiente di ordinamenti per implementare in modo efficiente log (n), inserire e rimuovere. Ovviamente, questo è molto più leggero di un albero binario in piena regola e avrà le stesse prestazioni nel complesso. – Tarydon

+1

Per chiarire: questo PriorityQueue non utilizza più spazio di un semplice elenco e ha log (n) inserisce e rimuove le prestazioni. – Tarydon

2

Cosa sarebbe così terribile per qualcosa di simile?

class PriorityQueue<TItem, TPriority> where TPriority : IComparable 
{ 
    private SortedList<TPriority, Queue<TItem>> pq = new SortedList<TPriority, Queue<TItem>>(); 
    public int Count { get; private set; } 

    public void Enqueue(TItem item, TPriority priority) 
    { 
     ++Count; 
     if (!pq.ContainsKey(priority)) pq[priority] = new Queue<TItem>(); 
     pq[priority].Enqueue(item); 
    } 

    public TItem Dequeue() 
    { 
     --Count; 
     var queue = pq.ElementAt(0).Value; 
     if (queue.Count == 1) pq.RemoveAt(0); 
     return queue.Dequeue(); 
    } 
} 

class PriorityQueue<TItem> : PriorityQueue<TItem, int> { } 
+1

Sembra pulito. Unity, per qualche ragione, non fornisce 'ElementAt()'. Quindi ho usato 'Valori [0]' lì. – noio

3

Questa è l'interfaccia esatta utilizzata dal mio highly optimized C# priority-queue.

È stato sviluppato appositamente per le applicazioni di ricerca percorso (A *, ecc.), Ma dovrebbe funzionare perfettamente anche per qualsiasi altra applicazione.

L'unico problema possibile è che, al fine di spremere le massime prestazioni assolute, l'implementazione richiede i valori accodati per estendere PriorityQueueNode.

public class User : PriorityQueueNode 
{ 
    public string Name { get; private set; } 
    public User(string name) 
    { 
     Name = name; 
    } 
} 

... 

HeapPriorityQueue<User> priorityQueue = new HeapPriorityQueue<User>(MAX_USERS_IN_QUEUE); 
priorityQueue.Enqueue(new User("Jason"), 1); 
priorityQueue.Enqueue(new User("Joseph"), 10); 

//Because it's a min-priority queue, the following line will return "Jason" 
User user = priorityQueue.Dequeue(); 
1

un po 'tardi, ma io aggiungo qui per riferimento

https://github.com/ERufian/Algs4-CSharp

chiave-valore-coppia code di priorità sono implementati in Algs4/IndexMaxPQ.cs, Algs4/IndexMinPQ.cs e Algs4/IndexPQDictionary.cs

Note:

  1. Se le priorità non sono IComparable' s, un costruttore di IC può essere specificato nel costruttore
  2. Invece di accodare l'oggetto e la sua priorità, ciò che è accodato è un indice e la sua priorità (e, per la domanda originale, sarebbe necessario un elenco separato o T [] convertire tale indice al risultato previsto)
Problemi correlati