前沿展望,.NET6 中的优先队列——PriorityQueue

序言

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。通常采用堆数据结构来实现。

1、前言

.NET出现20年了,还没有正式实现Priority Queue。它并没有阻止一起黑客攻击他们自己实现的优先级队列的事情,实际上,即使Microsoft也已经在框架内部埋藏了几种优先级队列的实现,但是从来没有向公众公开。最终,Microsoft参加了聚会并在.NET 6中实现了正式的优先队列。是的 .NET 6

如果您想要实现.NET Core,.NET 5甚至是.NET 4.6.X的实现,那么不幸的是,您很不幸。网上有很多实现,但是随着框架中正式的.NET Priority Queue逐渐消失,这些实现将逐渐消失。

2、什么是优先队列?

在开始之前,有必要先讨论一下优先队列到底是什么。优先级队列是一个队列,其中每个项目都具有一个“优先级”,可以将其与其他队列项目进行比较。将某个项目出队后,无论何时优先级最高的项目,它都会从队列中弹出。因此,如果我们认为标准队列是先进先出(FIFO),而堆栈类型是后进先出(LIFO),则优先级队列是…嗯。 。无论输入什么,最高优先级都先出!

优先级可能很复杂,正如我们将在实现自定义比较器时很快看到的那样,但是最简单的是它可能是一个数字,该数字越小(例如0为最高),优先级就越高。

优先级队列有很多用途,但是在进行“图形遍历”工作时最常见,因为您可以快速识别出具有最高/最低“成本”等的节点。 ,这不是太重要。真正要知道的是,那里有一个队列可以为您安排物品的优先级!

3、优先队列基础

考虑一个非常基本的例子:

using System.Collections.Generic;

PriorityQueue<string, int> queue = new PriorityQueue<string, int>();
queue.Enqueue("Item A", 0);
queue.Enqueue("Item B", 60);
queue.Enqueue("Item C", 2);
queue.Enqueue("Item D", 1);

while (queue.TryDequeue(out string item, out int priority))
{
    Console.WriteLine($"Popped Item : {item}. Priority Was : {priority}");
}

运行后的记过如下:

Popped Item : Item A. Priority Was : 0
Popped Item : Item D. Priority Was : 1
Popped Item : Item C. Priority Was : 2
Popped Item : Item B. Priority Was : 60

整数越小,优先级越高,我们可以看到始终根据此优先级弹出项目,无论它们添加到队列的顺序如何。我希望我可以扩展本教程的内容,但是…真的就是这么简单!

4、使用自定义比较器

上面的示例相对容易理解,因为优先级不过是整数。但是,如果我们对如何推导优先级有复杂的逻辑怎么办?我们可以自己构建此逻辑,并且仍然使用整数优先级,或者可以使用自定义比较器。让我们做后者!

假设我们正在构建银行应用程序。这是伦敦市中心的一家银行,因此优先服务以名字为“先生”的任何人。即使他们出现在队列的后面,也应该首先为他们服务(我知道这是令人恶心的…现况!)。

我们需要做的第一件事是找到一种比较标题的方法。为此,这段代码应该可以解决问题:

class TitleComparer : IComparer<string>
{
    public int Compare(string titleA, string titleB)
    {
        var titleAIsFancy = titleA.Equals("sir", StringComparison.InvariantCultureIgnoreCase);
        var titleBIsFancy = titleB.Equals("sir", StringComparison.InvariantCultureIgnoreCase);


        if (titleAIsFancy == titleBIsFancy) //If both are fancy (Or both are not fancy, return 0 as they are equal)
        {
            return 0;
        }
        else if (titleAIsFancy) //Otherwise if A is fancy (And therefore B is not), then return -1
        {
            return -1;
        }
        else //Otherwise it must be that B is fancy (And A is not), so return 1
        {
            return 1;
        }
    }
}

我们只是继承自IComparer,其中T是我们正在比较的类型。在我们的例子中,它只是一个简单的字符串。接下来,我们检查每个传入的字符串是否都是“先生”一词。然后在此基础上进行订购。通常,比较器应返回以下内容:

  • 如果基于的两个项目相等,则返回0
  • 如果将第一个项目比第二个项目“更高”或具有更高的优先级,则返回-1
  • 如果第二个项目的优先级高于第一个项目,则返回1
    现在,当我们创建队列时,我们可以像这样简单地传递新的比较器:
PriorityQueue<string, string> bankQueue = new PriorityQueue<string, string>(new TitleComparer());
bankQueue.Enqueue("John Jones", "Sir");
bankQueue.Enqueue("Jim Smith", "Mr");
bankQueue.Enqueue("Sam Poll", "Mr");
bankQueue.Enqueue("Edward Jones", "Sir");

Console.WriteLine("Clearing Customers Now");
while (bankQueue.TryDequeue(out string item, out string priority))
{
    Console.WriteLine($"Popped Item : {item}. Priority Was : {priority}");
}

输出结果如下:

Clearing Customers Now
Popped Item : John Jones. Priority Was : Sir
Popped Item : Edward Jones. Priority Was : Sir
Popped Item : Sam Poll. Priority Was : Mr
Popped Item : Jim Smith. Priority Was : Mr

5、何时制定优先级?

我想了解的是何时制定优先级?是在入队时,还是在我们出队时?还是两者兼而有之?

为了找出答案,我编辑了自定义比较器以执行以下操作:

Console.WriteLine($"Comparing {titleA} and {titleB}");

然后使用与上面相同的入队/出队,运行代码,这就是我看到的内容:

Comparing Mr and Sir
Comparing Mr and Sir
Comparing Sir and Sir
Clearing Customers Now
Comparing Mr and Mr
Comparing Sir and Mr
Popped Item : John Jones. Priority Was : Sir
Comparing Mr and Mr
Popped Item : Edward Jones. Priority Was : Sir
Popped Item : Sam Poll. Priority Was : Mr
Popped Item : Jim Smith. Priority Was : Mr

有趣的是,我们可以看到,当我入队时,肯定有比较,但仅是比较第一个节点。因此,作为一个示例,我们在顶部看到3个比较。那是因为我添加了4项。这告诉我,只有一个比较可以比较最顶层的项,否则可能会“堆积”。

接下来,请注意,当我调用Dequeue时,也有一些比较。.老实说,我不确定为什么会这样。具体来说,当我实际上假设只有一个比较时会发生两个比较(将队列的当前头与下一个比较)。

下次弹出一个项时,我们再次看到一个比较。最后,在最后两个流行音乐中,完全没有比较。

我很想解释一下所有这些工作原理,但是到现在为止,这很可能让我感到头疼!话虽这么说,很有趣的是,Priority不是“仅”在Enqueue上算出来的,因此,如果您的IComparer速度慢或太重,它的运行时间可能会比您想象的要多。

6、源码

话虽这么说,源码当然是开放的,因此非常欢迎您亲自去理解并发表评论!
您可以在2015年在以下位置查看PriorityQueue的原始提案:https : //github.com/dotnet/runtime/issues/14032。最重要的是,它使社区可以洞悉决策的方式和原因。不仅如此,还针对不同的方法提供了基准测试,并解释了为什么某些事情没有纳入Priority Queue API的第一版的原因。这真的是很棒的东西!

7、小结

在这里插入图片描述

微软.NET5发布不到1年,.NET6即将来袭,对于.NET编程人员来说,这是一个非常好的消息,但 挑战也接踵而至!

你,还学得动吗?

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

webmote

如果能帮到你,请支持下博主

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值