.Net中的优先级队列


216

我正在寻找优先级队列或堆数据结构的.NET实现

优先级队列是一种数据结构,比简单的排序更具灵活性,因为它们允许新元素以任意间隔进入系统。将新作业插入优先级队列比在每次到达时重新排序所有内容更具成本效益。

基本优先级队列支持三个主要操作:

  • 插入(Q,x)。给定带有键k的项目x,将其插入优先级队列Q。
  • 查找最小值(Q)。返回一个指针,该指针的项的值小于优先级队列Q中的任何其他项。
  • 最小删除(Q)。从密钥最小的优先级队列Q中删除该项目

除非我在错误的地方寻找,否则框架中没有一个。有谁知道一个好人,还是我应该自己做一个?


34
仅供参考,我已经开发了一个易于使用,高度优化的C#优先级队列,可以在此处找到。它是专门为寻路应用程序(A *等)而开发的,但也应适用于任何其他应用程序。我将其发布为答案,但问题最近已经关闭……
BlueRaja-Danny Pflughoeft13年

1
ParallelExtensionsExtras具有ConcurrentPriorityQueue 代码。msdn.microsoft.com/ ParExtSamples
VoteCoffee 2014年

毫不客气地引入PriorityQueue,作为将Java并发API移植到Spring.Net的.net的努力的一部分。具有完全通用支持的堆和队列。二进制文件可以在这里下载。
肯尼思·许

@ BlueRaja-DannyPflughoeft您能补充一下答案吗?
mafu

1
只是总结一下。.net中没有堆数据结构,.net核心中也没有。虽然Array.Sort大量使用它。存在内部实现
Artyom

Answers:


44

我喜欢将PowerCollections中OrderedBagOrderedSet类用作优先级队列。


60
OrderedBag / OrderedSet所做的工作比必要的多,它们使用红黑树而不是堆。
2009年

3
@DanBerindei:如果您需要进行运行计算(删除旧项),则不需要做任何工作,堆仅支持删除最小或最大
Svisstack 2014年

69

您可能会喜欢C5通用收藏库中的 IntervalHeap 。引用用户指南

类使用存储为成对数组的间隔堆来IntervalHeap<T>实现接口IPriorityQueue<T>。FindMin和FindMax操作以及索引器的get访问器花费时间O(1)。DeleteMin,DeleteMax,Add和Update操作以及索引器的set-accessor花费时间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
我建议您在ExtractDomination中清除堆值,这样它对引用对象的保留时间不会超过必需的时间(潜在的内存泄漏)。对于值类型,显然不关心它。
Wout,2015年

5
很好,但是您无法从中删除项目?这是优先级队列的重要操作。
汤姆·拉克沃西

看起来基础对象是一个数组。作为二叉树,这会更好吗?
Grunion Shaftoe

1
@OhadSchneider非常非常酷,我只是在研究最小堆,并试图做您所做的使它成为通用堆和最小堆或最大堆的事情!做得好
Gilad

1
@Gilad IEqualityComparer<T>是不够的,因为那只会告诉您两个项目是否相等,而您需要知道它们之间的关系(较小/较大)。我的确可以使用IComparer<T>……
Ohad Schneider

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 2011年

2
是的。当您调用Enqueue方法时,它将把该项目添加到该优先级的队列中。(排队方法中其他部分的内容。)
kobi7 2011年

5
“在计算机科学意义上,这实际上不是优先队列”是什么意思?怎么样使您相信这不是优先级队列?
Mark Byers

13
-1(不使用泛型)。
cdiggins 2013年

2
Heap / PriorityQueue的最大好处之一是最小/最大提取的O(1)复杂度,即Peek操作。这里涉及枚举器设置,for循环等。为什么!?同样,“ Enqueue”操作而不是O(logN)-堆的另一个关键功能,因为“ ContainsKey”,所以一次刷O(longN),第二次(再次是O(longN))添加了队列条目(如果需要的话),第三个用于实际检索队列(storage [prio]行),最后是线性添加到该队列。鉴于核心算法的实现,这确实是疯狂的。
Jonan Georgiev's


9

Microsoft .NET的集合中所述,Microsoft在.NET Framework中编写(并在线共享)2个内部PriorityQueue类。他们的代码可供试用。

编辑:正如@ mathusum-mut所评论的那样,Microsoft内部的PriorityQueue类之一中存在错误(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个人风格:)
Dong

3
但绝对不可读。考虑编写不会使问号浮在开发人员头上的代码。
alzaimar

3

在Java Collections框架的Java实现(java.util.PriorityQueue)上使用Java到C#转换器,或者更智能地使用该算法和核心代码,并将其插入您自己制作的C#类中,该类与C#Collections框架保持一致队列或至少集合的API。


这可行,但是不幸的是IKVM不支持Java泛型,因此您失去了类型安全性。
机械蜗牛

8
处理已编译的Java字节码时,没有“ Java泛型”之类的东西。IKVM不支持它。
标记

3

算法套件

我写了一个叫做AlgoKit的开源库,可以通过NuGet获得。它包含了:

  • 隐式D堆(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);

为什么那三堆?

实现的最佳选择在很大程度上取决于输入,如Larkin,Sen和Tarjan在优先队列的回归基础的经验研究中所显示的arXiv:1403.0252v1 [cs.DS]。他们测试了隐式d-ary堆,配对堆,Fibonacci堆,二项式堆,显式d-ary堆,等级配对堆,地震堆,违规堆,松弛等级的弱堆和严格的Fibonacci堆。

AlgoKit具有三种类型的堆,这些堆在测试的堆中似乎效率最高。

选择提示

对于相对较少的元素,您可能会对使用隐式堆特别是四元堆(隐式4进制)感兴趣。如果要在较大的堆大小上进行操作,则二项式堆和配对堆之类的摊销结构应具有更好的性能。



1

我最近遇到了同样的问题,最终为此创建了一个NuGet包

这实现了一个基于堆的标准优先级队列。它还具有BCL集合的所有常规功能:ICollection<T>以及IReadOnlyCollection<T>实现,自定义IComparer<T>支持,指定初始容量的能力以及DebuggerTypeProxy使该集合更易于在调试器中使用的功能。

该软件包还有一个内联版本,该版本仅将一个.cs文件安装到您的项目中(如果要避免采用外部可见的依赖关系,则很有用)。

更多信息可以在github页面上找到


1

一个简单的最大堆实现。

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将失败(并返回false)。所以...如果A.Compare(B)== 0并且A已经在列表中,则PriorityQueue.Enqueue函数将静默失败。
约瑟夫

介意什么是T xK key?我猜这是允许重复的技巧T x,我需要生成一个唯一的密钥(例如UUID)吗?
Thariq Nugrohotomo '18年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.