C#中的简单优先级队列-有什么比使用自定义排序器的列表更好:IComparer?

C#中的简单优先级队列-有什么比使用自定义排序器的列表更好:IComparer?,c#,priority-queue,C#,Priority Queue,我想实现一个优先级队列,它将我的对象-节点注入到队列中,与一个字段-f相关。我已经使用自定义比较器编写了列表,但这需要我: 排队-每次插入后对列表进行排序 出列-删除最后一个(而不是性能第一个),如下所示 我的列表应该总是按照某个字段排序(这里我需要按f排序)。我还需要能够添加列表中具有最低值的对象并将其出列 有人能告诉我最好的方法是什么吗 编辑 有一个非常好的答案,但我刚刚意识到我应该能够在这个容器中存储副本。如果您在.NET 4或更高版本中,您可以使用带有自定义IComparer的类

我想实现一个优先级队列,它将我的对象-
节点
注入到队列中,与一个字段-
f
相关。我已经使用自定义比较器编写了列表,但这需要我:

  • 排队-每次插入后对列表进行排序
  • 出列-删除最后一个(而不是性能第一个),如下所示

我的列表应该总是按照某个字段排序(这里我需要按
f
排序)。我还需要能够添加列表中具有最低值的对象并将其出列

有人能告诉我最好的方法是什么吗

编辑


有一个非常好的答案,但我刚刚意识到我应该能够在这个容器中存储副本。

如果您在.NET 4或更高版本中,您可以使用带有自定义
IComparer的类

该类允许您使用所有可变集合通用的
add
方法添加新对象。您可以使用
max
属性检索max元素,然后调用
Remove
从集合中删除max


编辑:(回答问题的编辑)如果需要存储副本,可以使用,并对其进行计数。同样,您也可以选择使用自定义的
IComparer
。当您将一个元素排入队列时,检查它是否已经存在,并增加其计数。当退出队列时,再次检查计数,将其递减,只有当计数达到零时才删除键。

如前所述,
SortedDictionary
可以部分实现此目的,您只需要一种处理重复优先级的方法。处理任何基于地图的结构(
字典
分类字典
,等等)中的重复项的方法是使值成为您真正想要的值的集合。在您的情况下,将值设置为
队列
最有意义

这是一个起点。如果需要,可以添加其他方法,如
Count
、实现
IEnumerable

/// <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;
    }
}
//
/// 
/// 
///存储的实际元素的类型
///优先级的类型。可能是int或long\
///但任何可以作为SortedDictionary键的类型都可以。
公共类优先队列
{
私有SortedDictionary=新SortedDictionary();
私有函数选择器;
公共优先级队列(函数选择器)
{
this.selector=选择器;
}
公共无效排队(远程通讯项目)
{
TKey=选择器(项目);
排队;
if(!dictionary.TryGetValue(key,out queue))
{
队列=新队列();
添加(键、队列);
}
排队。排队(项目);
}
公共远程通信退出队列()
{
如果(dictionary.Count==0)
抛出新异常(“没有要出列的项目:”);
var key=dictionary.Keys.First();
var queue=dictionary[key];
var output=queue.Dequeue();
如果(queue.Count==0)
删除(键);
返回输出;
}
}

您也可以将
分拣数据集
与副本一起使用。不是将值存储在集合中,而是存储列表的索引,然后在比较重复项时使用索引作为区分因素

List<int> nums = new List<int>{1,2,1,3,3,2};
SortedSet<int> pq = new SortedSet<int>(Comparer<int>.Create(
    (x,y) => nums[x] != nums[y] ? nums[x]-nums[y] : x-y
));
    
for (int i=0; i<nums.Count; i++) pq.Add(i);
while (pq.Any())
{
    Console.Write(nums[pq.Min] + " ");
    pq.Remove(pq.Min);
}
List nums=新列表{1,2,1,3,3,2};
SortedSet pq=新的SortedSet(Comparer.Create(
(x,y)=>nums[x]!=nums[y]?nums[x]-nums[y]:x-y
));
对于(int i=0;i nums[x]!=nums[y]?nums[x]-nums[y]:x-y
));

对于(int i=0;i可能的重复:@Blachshma重复的日期早于.NET 4.0,所以当时最好的答案是使用外部库。您可以使用Lucene.NET的优先级队列实现。答案很好,但实际上我刚刚意识到,只要密钥类型是对象而不是原语,您可以首先通过感兴趣的值进行比较,然后通过任意值进行比较,这保证是唯一的。您的解决方案很好,但存在一个问题。在
SortedDictionary
方法中,您只存储了
K
的一个值。可能有两个不同的实例表现不同,但是值是相等的。因此,在存在重复值的情况下,您将只对第一个值进行解列。您的性能更高,但Servy的回答是“更正确”,使用
SortedDictionary
不会使解列操作
O(logn)
?这是因为该字典的
remove
方法是
O(logn)
根据文档,您也可以将SortedSet与重复项一起使用。将索引存储到集合中,并使用它来区分重复项。在
出列
方法中,您可以通过如下方式获得
键值对来避免查找:
字典。First()
,因此您可以同时获得
key
queue
。没什么大不了的,也许更好一点。但是,退出
O(logn)
不是因为使用了
字典。删除(key)
O(logn)
?这不会破坏所需的
O(1)
priority queue的出列?@Dragolis我没有看到我说这是O(1)的地方。但是,是的,添加和删除项目在最坏的情况下是O(log(n))其中n是所使用的唯一优先级值的数量。请注意,它不随排队项目的数量而扩展,而是随唯一优先级值的数量而扩展,这可能比总项目的数量小得多。如果可能的唯一优先级值的数量是常数,则整个情况实际上是O(1)正因为如此。当您不使用新的优先级值时,也将项目排队和退队为O(1)。此解决方案不会
List<int> nums = new List<int>{1,2,1,3,3,2};
SortedSet<int> pq = new SortedSet<int>(Comparer<int>.Create(
    (x,y) => nums[x] != nums[y] ? nums[x]-nums[y] : x-y
));
    
for (int i=0; i<nums.Count; i++) pq.Add(i);
while (pq.Any())
{
    Console.Write(nums[pq.Min] + " ");
    pq.Remove(pq.Min);
}
public static void Main()
{
    List<int> nums = new List<int>{1,2,1,3,3,2};
    SortedSet<int> pq = new SortedSet<int>(Comparer<int>.Create(
        (x,y) => nums[x] != nums[y] ? nums[x]-nums[y] : x-y
    ));
    
    for (int i=0; i<nums.Count; i++) Enqueue(pq, i);
    while (pq.Any())
    {
        int topIndex = Dequeue(pq);
        Console.Write(nums[topIndex] + " ");
    }
}

private static void Enqueue(SortedSet<int> pq, int numIndex)
{
    pq.Add(numIndex);
}

private static int Dequeue(SortedSet<int> pq)
{
    int topIndex = pq.Min;
    pq.Remove(pq.Min);
    return topIndex;
}