Очередь приоритетов C # - PullRequest
       8

Очередь приоритетов C #

6 голосов
/ 21 декабря 2009

Я ищу приоритетную очередь с интерфейсом, подобным этому:

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

    public T Dequeue()
    {
    }
}

Все реализации, которые я видел, предполагают, что item является IComparable, но мне не нравится этот подход; Я хочу указать приоритет, когда помещаю его в очередь.

Если готовой реализации не существует, каков наилучший способ сделать это сам? Какую базовую структуру данных я должен использовать? Какое-то самобалансирующееся дерево или как? Было бы неплохо использовать стандартную структуру C # .net.

Ответы [ 9 ]

11 голосов
/ 21 декабря 2009

Если у вас есть существующая реализация очереди приоритетов на основе IComparable, вы можете легко использовать ее для построения необходимой вам структуры:

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;
  }
}
6 голосов
/ 21 декабря 2009

Вы можете добавить проверки безопасности, а что нет, но вот очень простая реализация, использующая 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;
    }
}

Я предполагаю, что меньшие значения priority соответствуют элементам с более высоким приоритетом (это легко изменить).

Если несколько потоков будут получать доступ к очереди, вам также потребуется добавить механизм блокировки. Это легко, но дайте мне знать, если вам нужно руководство здесь.

SortedList реализован внутри как двоичное дерево.

В приведенной выше реализации требуются следующие вспомогательные классы. Это адрес комментария Лассе В. Карлсена о том, что элементы с одинаковым приоритетом не могут быть добавлены с использованием простой реализации с использованием 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);
        }
    }
}
5 голосов
/ 21 декабря 2009

Вот очень простая облегченная реализация, которая имеет производительность O (log (n)) для push и pop Он использует структуру данных кучи , построенную поверх списка .

/// <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> ();
}
5 голосов
/ 21 декабря 2009

Вы можете написать обертку вокруг одной из существующих реализаций, которая изменяет интерфейс по вашему усмотрению:

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;
    }
}

Это то же самое, что и предложение Итоулсона. Мне просто потребовалось больше времени, чтобы написать свой, потому что я заполнил больше методов. : -s

4 голосов
/ 26 июля 2013

Это точный интерфейс, используемый моей высокооптимизированной очередью приоритетов C # .

Он был разработан специально для приложений поиска пути (A * и т. Д.), Но должен отлично работать и для любого другого приложения.

Единственная возможная проблема заключается в том, что для того, чтобы выжать абсолютную максимальную производительность, реализация требует, чтобы значения в очереди были расширены 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();
2 голосов
/ 21 декабря 2009

Что может быть такого ужасного в этом?

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 голос
/ 15 октября 2014

Немного поздно, но я добавлю это сюда для справки

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

Очереди приоритетов пары ключ-значение реализованы в Algs4 / IndexMaxPQ.cs, Algs4 / IndexMinPQ.cs и Algs4 / IndexPQDictionary.cs

Примечания:

  1. Если приоритеты не являются IComparable, то IComparer может быть указан в конструкторе
  2. Вместо постановки в очередь объекта и его приоритета ставится в очередь индекс и его приоритет (и для первоначального вопроса потребуется отдельный список или T [], чтобы преобразовать этот индекс в ожидаемый результат)
1 голос
/ 18 февраля 2013

Я понимаю, что ваш вопрос специально требует реализации, не основанной на IComparable, но я хочу отметить недавнюю статью от Visual Studio Magazine.

http://visualstudiomagazine.com/articles/2012/11/01/priority-queues-with-c.aspx

Эта статья с @ itowlson's может дать полный ответ.

0 голосов
/ 21 декабря 2009

Похоже, вы могли бы бросить свой собственный с сериями очередей, по одному на каждый приоритет. Словарь и просто добавьте его к соответствующему.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...