Черга з пріоритетом у .Net [закрита]


216

Я шукаю .NET реалізацію черги з пріоритетом чи структуру даних купи

Черги з пріоритетом - це структури даних, які забезпечують більшу гнучкість, ніж просте сортування, оскільки вони дозволяють новим елементам входити в систему з довільними інтервалами. Набагато вигідніше вставити нову роботу в черговість пріоритетних, ніж повторно сортувати все при кожному такому прибутті.

Основна черга пріоритетів підтримує три основні операції:

  • Вставка (Q, x). Давши елемент x з ключем k, вставте його у чергу пріоритетів Q.
  • Знайти-мінімум (Q). Поверніть вказівник на елемент, значення якого менше, ніж будь-який інший ключ у черзі пріоритетів Q.
  • Видалення-мінімум (Q). Видаліть елемент із черзі пріоритетів Q, ключ якого мінімальний

Якщо я не дивлюся в неправильному місці, його немає в рамках. Хтось знає про хороше, чи повинен я скрутити своє?


34
FYI Я розробив просту у використанні високооптимізовану черговість C # пріоритетів, яку можна знайти тут . Він був розроблений спеціально для додатків на шляху маршрутизації (A * та ін.), Але також повинен працювати ідеально для будь-якого іншого додатка. Я б опублікував це як відповідь, але питання нещодавно було закрито ...
BlueRaja - Danny Pflughoeft

1
ParallelExtensionsExtras має ConcurrentPriorityQueue code.msdn.microsoft.com/ParExtSamples
VoteCoffee

Безсоромно запроваджуйте PriorityQueue , як частину зусиль для того, щоб порту Java одночасно подавав API у .net для Spring.Net. Це і купа, і черга з повною загальною підтримкою. Бінарне можна завантажити тут .
Кеннет Сю

@ BlueRaja-DannyPflughoeft Чи можете ви додати відповідь?
мафу

1
Просто підсумувати. Немає структури даних купи в .net, ні в ядрі .net зараз. Хоча Array.Sort користувачі його для великої кількості. Внутрішні реалізації існують.
Артем

Відповіді:


44

Мені подобається використовувати OrderedBagі OrderedSetкласи в PowerCollections як черги з пріоритетом.


60
OrdersBag / OrdersSet роблять більше роботи, ніж потрібно, вони використовують червоно-чорне дерево замість купи.
Dan Berindei

3
@DanBerindei: не потрібно працювати, якщо вам потрібно зробити розрахунок (видалити старі елементи), купа підтримує лише видалення min або max
Svisstack

69

Можливо, вам сподобається IntervalHeap з Бібліотеки загальної колекції C5 . цитую інструкцію

Клас IntervalHeap<T>реалізує інтерфейс, IPriorityQueue<T>використовуючи інтервальну купу, що зберігається як масив пар. Для операцій FindMin і FindMax та доступу-аксесуара індексатора потрібен час O (1). Операції DeleteMin, DeleteMax, Add and Update та набір-аксесуар індексатора потребують часу O (log n). На відміну від звичайної черги з пріоритетом, інтервальна купа пропонує як мінімальну, так і максимальну операції з однаковою ефективністю.

API досить простий

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

Встановити з Nuget https://www.nuget.org/packages/C5 або GitHub https://github.com/sestoft/C5/


3
Здається, це дуже солідна бібліотека, і вона має 1400 одиниць тестів.
ECC-Dan

2
Я намагався його використовувати, але він має серйозні недоліки. IntervalHeap не має вбудованої концепції пріоритетності і змушує вас реалізувати IComparable або IComparer, роблячи це відсортовану колекцію не "пріоритетною". Ще гірше, що немає прямого способу оновити пріоритет якогось попереднього запису !!!
morteza khosravi

52

Ось моя спроба купівлі .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;
    }
}

Деякі тести:

[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
Я рекомендую очистити значення купи в ExtractDominating, щоб воно не трималось на посилається об'єкт довше, ніж потрібно (потенційна витік пам'яті). Для типів цінності це, очевидно, не викликає занепокоєння.
Wout

5
Приємно, але ви не можете видалити елементи з нього? Це важлива операція для черг пріоритетних.
Том Ларкуорті

Схоже, що основний об'єкт - це масив. Хіба це не було б краще як двійкове дерево?
Grunion Shaftoe

1
@OhadSchneider дуже круто, я просто дивився на міні-купу і намагався зробити те, що ви робили, роблячи це загальним і мінімальним або максимальним набором! чудова робота
Гілад

1
@Gilad IEqualityComparer<T>буде недостатньо, оскільки це лише скаже вам, чи рівні два предмети, тоді як вам потрібно знати співвідношення між ними (хто менший / більший). Це правда, що я міг би використовуватись, IComparer<T>хоча ...
Охад Шнайдер

23

ось я щойно написав, можливо, він не настільки оптимізований (просто використовує відсортований словник), але простий для розуміння. Ви можете вставляти об'єкти різного типу, тому немає загальних черг.

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

        }
    }
}

це не дозволяє зробити кілька записів з одним і тим же пріоритетом?
Letseatlunch

2
це робить. коли ви викликаєте метод Enqueue, він додасть елемент у чергу цього пріоритету. (участь у іншому способі
анкетування

5
Що ви маєте на увазі під "чергою, яка насправді не є пріоритетною чергою у значенні інформатики"? Що з цього приводу змушує вас повірити, що це не пріоритетна черга?
Марк Байєрс

14
-1 за не використання дженериків.
cdiggins

2
Однією з найбільших переваг Heue / PriorityQueue є складність O (1) min / max вилучення, тобто операція Peek. І тут це передбачає налаштування перечислювача, for-loop тощо. Чому !? Крім того, операція "Enqueue" замість того, щоб бути O (logN) - ще одна ключова особливість купи, має один перехід O (longN) через "ContainsKey", другий (знову O (longN)) для додавання запису черги (за потреби), третій, щоб фактично отримати Чергу (рядок [prio] зберігання), і, нарешті, лінійне додавання до цієї черги. Це справді божевільно з огляду на реалізацію основних алгоритмів.
Джонан Георгієв

10

Я знайшов його Джуліан Бакналл у своєму блозі тут - http://www.boyet.com/Articles/PriorityQueueCSharp3.html

Ми трохи змінили його, щоб з часом з елементами з низькою пріоритетністю з часом «вибухнути» до верху, щоб вони не зазнавали голоду.


9

Як уже згадувалося в колекціях Microsoft для .NET , Microsoft написала (і поділилася в Інтернеті) двома внутрішніми класами PriorityQueue в рамках .NET Framework. Їх код доступний для випробування.

EDIT: Як коментує @ mathusum-mut, в одному із внутрішніх класів PriorityQueue Microsoft є помилка (спільнота SO, звичайно, надала виправлення для неї): Помилка у внутрішній програмі Microsoft PriorityQueue <T>?


10
Виправлена помилка була виявлена в одній з реалізацій тут: stackoverflow.com/questions/44221454 / ...
MathuSum MUT

ой! Я бачу, що всі ці класи PriorityQueue<T>в спільному джерелі Microsoft позначені internalспецифікатором доступу. Тому вони використовуються лише внутрішніми функціональними рамками. Вони не доступні для загального споживання лише шляхом посилання windowsbase.dllна проект C #. Єдиний спосіб - скопіювати спільне джерело в сам проект всередині файлу класу.
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;
    }
}

легко.


13
Іноді я бачу такі речі на кшталт for (var n2 = n / 2; n > 0 && comparer.Compare(v, heap[n2]) > 0; n = n2, n2 /= 2) heap[n] = heap[n2]; і замислююся, чи варто було це прокладати

1
@DustinBreakey особистий стиль :)
Shimou Dong

3
але точно не читабельні для інших. Подумайте про написання коду, який не залишає позначки питання, що пливе вгорі голови розробника.
альзаймар

3

Використовуйте перекладач Java на C # для реалізації Java (java.util.PriorityQueue) в рамках Java Collections або більш розумно використовуйте алгоритм та основний код і підключіть його до власного класу C #, який дотримується рамки колекції C #. API для черг чи принаймні колекцій.


Це працює, але, на жаль, IKVM не підтримує дженерики Java, тому ви втрачаєте безпеку типу.
Механічний равлик

8
Немає такого поняття, як "дженерики Java", коли ви маєте справу зі зібраним байт-кодом Java. IKVM не може це підтримати.
Марк

3

AlgoKit

Я написав бібліотеку з відкритим кодом під назвою AlgoKit , доступну через NuGet . Це містить:

  • Неявні д- арські купи (ArrayHeap),
  • Біноміальні купи ,
  • Спарювання купи .

Код пройшов широку перевірку. Я напевно рекомендую спробувати.

Приклад

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

Чому ці три купи?

Оптимальний вибір реалізації є сильно залежним від вхідних даних - як показують Ларкін, Сен та Тарджан в емпіричному дослідженні « Основні емпіричні черги з пріоритетом» , arXiv: 1403.0252v1 [cs.DS] . Вони протестували неявні куби d-ary, парування купи, куби Фібоначчі, біноміальні купи, явні д-арні купи, купірувальні купи, землетрусні купи, групи порушень, рангові слабкі купи і суворі кучки Фібоначчі.

AlgoKit пропонує три типи купи, які виявилися найбільш ефективними серед перевірених.

Підказка на вибір

Для відносно невеликої кількості елементів вам, ймовірно, буде цікаво використовувати неявні купи, особливо четвертинні купи (неявна 4-арія). У разі експлуатації на великих розмірах купи, амортизовані структури, такі як біноміальні купи та з’єднання, повинні працювати краще.



1

У мене був такий самий випуск нещодавно, і я закінчив створення пакету NuGet для цього.

Це реалізує стандартну чергу пріоритетів на основі купи. У ньому також є всі звичні приналежності колекцій BCL: ICollection<T>та IReadOnlyCollection<T>реалізація, користувацька IComparer<T>підтримка, можливість задати початкову ємність таDebuggerTypeProxy полегшити роботу з колекцією в налагоджувачі.

Також є Inline версія пакету, яка просто встановлює один файл .cs у ваш проект (корисно, якщо ви хочете уникати зовнішньо-видимих ​​залежностей).

Більше інформації можна знайти на сторінці github .


1

Проста реалізація Max Max.

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

Наступна реалізація PriorityQueueзастосувань SortedSetіз системної бібліотеки.

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 вийде з ладу (і поверне помилкове значення), якщо у вас вже є елемент у наборі з тим самим "пріоритетом", що і елемент, який ви намагаєтеся додати. Отже ... якщо A.Compare (B) == 0 і A вже є у списку, функція PriorityQueue.Enqueue мовчки вийде з ладу.
Йосип

Розум пояснити, що таке T xі K key? Я здогадуюсь, це хитрість дозволити дублікат T x, і мені потрібно створити унікальний ключ (наприклад, UUID)?
Thariq Nugrohotomo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.