C#进阶学习(十七)PriorityQueue<TElement, TPriority>优先级队列的介绍

1. PriorityQueue是什么?作用是什么?

定义PriorityQueue<TElement, TPriority> 是 C# (.NET 6+ 引入) 中的泛型优先级队列数据结构。

那么是什么是优先级队列呢?优先级队列 是一种抽象数据结构,其核心特性是元素按照优先级顺序 进行管理,而非传统队列的先进先出(FIFO)规则。在优先级队列中,每次被取出处理的元素是当前队列中优先级最高的(或最低的,取决于具体实现),而非最早进入队列的元素。这种特性使其在需要动态调整处理顺序的场景中具有独特优势。

作用 :按优先级管理元素,优先级高(值小)的元素先出队。常用于需要按特定顺序处理任务的场景,如:

任务调度(高优先级任务优先执行)。

算法实现(如 Dijkstra 最短路径算法、Huffman 编码)。

事件驱动系统(按时间戳处理事件)。

2. 底层实现与复杂度

底层结构 :基于 二叉堆(最小堆) 实现,确保堆顶元素始终是优先级最高的(即优先级值最小)。

  • 时间复杂度

    入队 (Enqueue):O(log n)

    出队 (Dequeue):O(log n)

    查看堆顶 (Peek):O(1)

  • 空间复杂度O(n)(动态数组存储堆节点)。

3.优先级的次序

在 C# 的 PriorityQueue<TElement, TPriority> 中,优先级规则完全由 TPriority 类型的比较逻辑决定。默认的次序如下:

(1) 数值类型(如 intdouble 等)

默认规则 :优先级值 越小 的优先级越高(即最小堆)。

示例

cs 复制代码
var queue = new PriorityQueue<string, int>();
queue.Enqueue("Task 1", 3);
queue.Enqueue("Task 2", 1); // 优先级更高
queue.Enqueue("Task 3", 2);
// 出队顺序:Task 2 → Task 3 → Task 1

(2) 字符串类型(string

默认规则 :按 字典序(区分大小写)升序排序。

比较规则:"A" < "B" < "a" < "b"(基于 Unicode 码点)。

示例:

cs 复制代码
var queue = new PriorityQueue<string, string>();
queue.Enqueue("Apple", "Zebra"); // 优先级低
queue.Enqueue("Banana", "Apple"); // 优先级高
// 出队顺序:Banana(Apple) → Apple(Zebra)

小结,默认里面是按照升序进行组成的,每次默认弹出的是优先级最高的,想自定义出队列规则,需要自定义比较器 。若要覆盖默认规则(如让数值越大优先级越高),需实现 IComparer<TPriority> 接口,并在构造函数中传入比较器。例如:

示例 1:数值降序优先级(最大堆)
cs 复制代码
using System.Collections.Generic;

// 自定义比较器:数值越大优先级越高
public class MaxPriorityComparer : IComparer<int>
{
    public int Compare(int x, int y) => y.CompareTo(x); // 反转默认顺序
}

public class Program
{
    public static void Main()
    {
        // 使用自定义比较器
        var queue = new PriorityQueue<string, int>(new MaxPriorityComparer());
        queue.Enqueue("Task 1", 3); // 优先级高(数值大)
        queue.Enqueue("Task 2", 1); // 优先级低
        queue.Enqueue("Task 3", 2);

        // 出队顺序:Task 1 → Task 3 → Task 2
    }
}
示例 2:字符串按长度排序
cs 复制代码
public class StringLengthComparer : IComparer<string>
{
    public int Compare(string x, string y) => x.Length.CompareTo(y.Length);
}

public class Program
{
    public static void Main()
    {
        var queue = new PriorityQueue<string, string>(new StringLengthComparer());
        queue.Enqueue("Apple", "Zebra");    // 长度 5
        queue.Enqueue("Banana", "Ant");     // 长度 3 → 优先级更高
        queue.Enqueue("Cherry", "Elephant");// 长度 8

        // 出队顺序:Banana → Apple → Cherry
    }
}

小结:

场景 规则 实现方式
默认优先级(数值升序) 值越小优先级越高 无需额外代码,直接使用默认队列
数值降序(最大堆) 值越大优先级越高 自定义 IComparer<int>
字符串按长度排序 长度越短优先级越高 自定义 IComparer<string>
不区分大小写的字符串排序 按字母顺序忽略大小写 自定义 IComparer<string>

4. 主要使用方式与代码示例

示例 1:基本使用(默认升序优先级)

cs 复制代码
using System;
using System.Collections.Generic;

var priorityQueue = new PriorityQueue<string, int>();

// 入队:元素 + 优先级(优先级值越小,优先级越高)
priorityQueue.Enqueue(element: "Task 1", priority: 3); // 低优先级
priorityQueue.Enqueue("Task 2", 1); // 高优先级
priorityQueue.Enqueue("Task 3", 2);

// 出队:按优先级升序(值小的先出)
while (priorityQueue.TryDequeue(out string task, out int priority))
{
    Console.WriteLine($"处理任务: {task}, 优先级: {priority}");
}
// 输出顺序:
// 处理任务: Task 2, 优先级: 1
// 处理任务: Task 3, 优先级: 2
// 处理任务: Task 1, 优先级: 3
示例 2:自定义降序优先级比较器

关于比较器的知识可以看这里:C#中的比较器

cs 复制代码
// 自定义比较器:优先级值越大,优先级越高
public class MaxPriorityComparer : IComparer<int>
{
    public int Compare(int x, int y) => y.CompareTo(x); // 降序
}

public class Program
{
    public static void Main()
    {
        // 使用自定义比较器初始化队列
        var queue = new PriorityQueue<string, int>(new MaxPriorityComparer());
        
        queue.Enqueue("Task A", 5);
        queue.Enqueue("Task B", 10); // 优先级更高(值更大,但比较器反转逻辑)

        while (queue.TryDequeue(out var task, out var priority))
        {
            Console.WriteLine(task); // 输出顺序:Task B → Task A
        }
    }
}
示例 3:处理队列为空的情况
cs 复制代码
var queue = new PriorityQueue<char, int>();
queue.Enqueue('A', 1);

// 安全出队(避免异常)
if (queue.TryDequeue(out char element, out int priority))
{
    Console.WriteLine($"出队元素: {element}");
}

if (!queue.TryDequeue(out element, out priority))
{
    Console.WriteLine("队列为空!");
}

5. 主要 API 和属性汇总

属性
属性名 类型 说明
Count int 队列中当前元素的数量。
方法
方法名 参数 返回值/行为
Enqueue(TElement element, TPriority priority) element: 元素;priority: 优先级 将元素按优先级插入队列。
Dequeue() 移除并返回优先级最高的元素。若队列为空,抛出 InvalidOperationException
TryDequeue(out TElement element, out TPriority priority) element: 出队元素;priority: 优先级 安全出队,返回是否成功。若队列为空,返回 false
Peek() 返回优先级最高的元素但不移除。若队列为空,抛出异常。
TryPeek(out TElement element, out TPriority priority) element: 堆顶元素;priority: 优先级 安全查看堆顶元素,返回是否成功。若队列为空,返回 false
Clear() 清空队列中的所有元素。
构造函数
构造函数 说明
PriorityQueue() 使用默认优先级比较器(升序)。
PriorityQueue(IComparer<TPriority>? comparer) 指定自定义优先级比较器。
PriorityQueue(IEnumerable<(TElement Element, TPriority Priority)> items) 从现有元素集合初始化队列。

其中两个重要方法的解释:

构造函数 PriorityQueue(IEnumerable<(TElement, TPriority)> items)

作用:通过一个包含元素及其优先级的元组集合 批量初始化优先级队列 ,直接构建堆结构,避免多次调用 Enqueue 的开销。说白了,其实就是初始化,你直接塞一个这样的组合进去。

特点:

批量添加优化:

时间复杂度:O(n)O(n)(直接构建堆,比逐个调用 Enqueue 的 O(nlog⁡n)O(nlogn) 更高效)。空间复杂度:O(n)O(n)(存储所有元素)。

自动堆化(Heapify):

内部会将传入的集合直接转换为堆结构,无需手动调整顺序。

支持自定义比较器:

可结合其他构造函数指定优先级比较规则。就是,你在初始化之后,直接往后面,塞一个比较器,他是支持这个重载的。

示例:

cs 复制代码
using System;
using System.Collections.Generic;

var items = new List<(string Element, int Priority)>
{
    ("Task 1", 3),
    ("Task 2", 1), // 高优先级
    ("Task 3", 2)
};

// 使用集合初始化队列
var queue = new PriorityQueue<string, int>(items);

// 出队顺序:Task 2 → Task 3 → Task 1
while (queue.TryDequeue(out var task, out var priority))
{
    Console.WriteLine($"{task} (优先级: {priority})");
}

方法 TryDequeue(out TElement element, out TPriority priority)

作用:安全地取出并删除优先级最高的元素,避免队列为空时抛出异常。适合不确定队列状态时的安全操作。

特点:

返回值

true:成功取出元素,elementpriority 参数被正确赋值。

false:队列为空,elementpriority 赋为 default(TElement)default(TPriority)

非破坏性检查:

Dequeue() 不同,此方法不会在队列为空时抛出异常,而是通过返回值明确操作结果。

例如:

cs 复制代码
var queue = new PriorityQueue<char, int>();
queue.Enqueue('A', 3);
queue.Enqueue('B', 1);

// 安全出队
if (queue.TryDequeue(out char element, out int priority))
{
    Console.WriteLine($"出队元素: {element} (优先级: {priority})"); // B (优先级: 1)
}

// 队列未空时继续操作
if (queue.TryDequeue(out element, out priority))
{
    Console.WriteLine($"出队元素: {element} (优先级: {priority})"); // A (优先级: 3)
}

// 队列已空时处理
if (!queue.TryDequeue(out element, out priority))
{
    Console.WriteLine("队列为空!"); // 输出此句
    Console.WriteLine($"element 的默认值: {element}"); // 输出 '\0'(char 的默认值)
    Console.WriteLine($"priority 的默认值: {priority}"); // 输出 0(int 的默认值)
}
Dequeue() 的对比
方法 行为 适用场景
Dequeue() 直接取出元素,队列为空时抛出异常 确定队列非空时简化代码
TryDequeue(...) 安全取出元素,通过返回值处理空队列情况 不确定队列状态时避免异常

6.注意事项

(1) PriorityQueue<TElement, TPriority>.NET 6 及以上版本中引入的泛型数据结构,仅在 .NET 6、.NET 7、.NET 8 及后续版本中可用。若项目目标框架为旧版本(如 .NET 5、.NET Core 3.1 或更早),则无法直接使用此类型。需通过升级项目框架或寻找替代方案(如第三方库)解决兼容性问题。

(2)PriorityQueue<TElement, TPriority> 默认允许插入重复元素(即使元素值和优先级均相同)。若需确保队列中元素的唯一性,需自行维护逻辑,例如通过 HashSet<TElement> 跟踪已存在的元素,仅在元素未添加时调用 Enqueue

(3)默认值陷阱 :当队列为空时,TryDequeue 方法会将输出参数设为 TElementTPriority 的默认值(如 int0,引用类型为 null)。需通过返回值判断操作是否成功,避免误用默认值导致逻辑错误。异常风险 :直接调用 Dequeue()Peek() 方法时,若队列为空会抛出 InvalidOperationException。建议优先使用 TryDequeueTryPeek 方法,以规避异常风险。

相关推荐
虾球xz15 分钟前
游戏引擎学习第250天:# 清理DEBUG GUID
c++·学习·游戏引擎
martian66516 分钟前
信创系统图形界面开发指南:技术选择与实践详解
开发语言·科技·系统架构·系统安全·创业创新
我命由我1234517 分钟前
STM32 开发 - stm32f10x.h 头文件(内存映射、寄存器结构体与宏、寄存器位定义、实现点灯案例)
c语言·开发语言·c++·stm32·单片机·嵌入式硬件·嵌入式
kukuwawu29 分钟前
基因组注释笔记——GeneMark-ES/ET的使用
经验分享·笔记·学习·bash·基因注释
ghost14340 分钟前
C#学习第20天:垃圾回收
开发语言·学习·c#
oioihoii1 小时前
C++23 std::invoke_r:调用可调用 (Callable) 对象 (P2136R3)
开发语言·c++23
一眼青苔1 小时前
conda添加新python版本环境,如何激活和销毁
开发语言·python·conda
Pseudo…1 小时前
web技术与Nginx网站服务
开发语言·php
咛辉1 小时前
spark和Hadoop之间的对比和联系
开发语言
Humbunklung2 小时前
Sigmoid函数简介及其Python实现
开发语言·python·深度学习·机器学习