Simple priority queue in C# - what will be better

2020-06-20 05:42发布

问题:

I would like to implement a priority queue which would inject my objects - Nodes to the queue with respect to one field - f. I have already written List with custom comparer but this would require from me to:

  • enqueue - sort the List after each insertion
  • dequeue - remove the last(instead of first for performance) like so

    myList.RemoveAt(myList.Count - 1);
    

My List should be always sorted according to some field (here I need it to be sorted by f). I also need the ability to add and dequeue the object with lowest value from the list.

Can someone tell me what would be the best approach to this ?

EDIT

dasblinkenlight has a very good answer but just I have realised that I should be able to store duplicates in this container.

回答1:

If you are in .NET 4 or later, you can use a SortedSet<T> class with a custom IComparer<T>.

The class lets you add new objects with the Add method common to all mutable collections. You can retrieve the max element using the Max property, and then call Remove to erase the max from the collection.

Edit: (in response to an edit of the question) If you need to store duplicates, you can use a SortedDictionary<Key,int>, and make a counted set out of it. Again, you have an option of using a custom IComparer<T>. When you enqueue an element, check if it exists already, and increment its count. When dequeueing, again check the count, decrement it, and remove the key only when the count reaches zero.



回答2:

As mentioned earlier, a SortedDictionary gets you part way there, you just need a way of dealing with duplicate priorities. The way of dealing with duplicates in any Map based structure (Dictionary, SortedDictionary, etc.) is to make the value a collection of what you really want the values to be. In your case, it makes the most sense for the value to be a Queue<T>.

Here is a starting place. You can add additional methods such as Count, implementations of IEnumerable, etc. if needed.

/// <summary>
/// 
/// </summary>
/// <typeparam name="TElement">The type of the actual elements that are stored</typeparam>
/// <typeparam name="TKey">The type of the priority.  It probably makes sense to be an int or long, \
/// but any type that can be the key of a SortedDictionary will do.</typeparam>
public class PriorityQueue<TElement, TKey>
{
    private SortedDictionary<TKey, Queue<TElement>> dictionary = new SortedDictionary<TKey, Queue<TElement>>();
    private Func<TElement, TKey> selector;


    public PriorityQueue(Func<TElement, TKey> selector)
    {
        this.selector = selector;
    }

    public void Enqueue(TElement item)
    {
        TKey key = selector(item);
        Queue<TElement> queue;
        if (!dictionary.TryGetValue(key, out queue))
        {
            queue = new Queue<TElement>();
            dictionary.Add(key, queue);
        }

        queue.Enqueue(item);
    }

    public TElement Dequeue()
    {
        if (dictionary.Count == 0)
            throw new Exception("No items to Dequeue:");
        var key = dictionary.Keys.First();

        var queue = dictionary[key];
        var output = queue.Dequeue();
        if (queue.Count == 0)
            dictionary.Remove(key);

        return output;
    }
}