在.Net 6,7,8 中C#提供了优先队列PriorityQueue<TElement,TPriority> 类,详情参见官方文档PriorityQueue<TElement,TPriority> 类 (System.Collections.Generic) ,在Unity中想直接使用这个类时,发现不支持,没办法只好自己写一个了,这里讲一下我的实现思路和源码:
百度百科定义:
优先队列是0个或多个元素的集合,每个元素都有一个优先权,对优先级队列执行的操作有
- ?查找
- 插入一个新元素
- 删除 一般情况下,查找操作用来搜索优先权最大的元素,删除操作用来删除该元素 。对于优先权相同的元素,可按先进先出次序处理或按任意优先权进行。
简单定义:
优先队列是一种特殊的队列,每次出队时移除队中最大(小)元素。
这篇文章的实现思路基于二叉堆来实现,二叉堆在逻辑上是一个完全二叉树结构。在完全二叉树中,除最低层之外,其他层都被节点填满,最低层尽可能从左到右插入节点。
根据节点的值与子节点的值的大小关系,堆又可以分为最大堆和最小堆。
完全二叉树结构很容易被数组存储和计算。根据完全二叉树的特性,已知完全二叉树上 i 位置上的节点,我们求得以下信息:
父节点位置:(i - 1) / 2
左孩子节点位置:2i + 1
右孩子节点位置:2i + 2
因此代码中,逻辑结构上基于完全二叉树实现,数据存储基于数组实现。
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Algorithm
{
public class PriorityQueue<T>{
private int _size;
public int Size
{
get{
if (_size < 0) _size = 0;
return _size;
}
set => _size = value;
}
private int _capacity;
public int Capacity {
get{
if (_capacity < 0) _capacity = 0;
return _capacity;
}
set => _capacity = value;
}
public T[] _elements;
public bool IsEmpty => _size == 0;
private T Top => _elements[0];
private readonly IComparer<T> _comparator;
public PriorityQueue(IComparer<T> comparator, int capacity = 1) {
Size = 0;
Capacity = capacity;
_elements = new T[capacity];
_comparator = comparator;
}
public void Enqueue(T element)
{
if (Size == Capacity) {
ExpandCapacity();
}
_elements[Size] = element;
HeapInsert(_elements, Size);
Size++;
}
public T Dequeue()
{
if (Size == 0) {
return default(T);
}
T element = _elements[0];
//删除堆顶元素,将堆顶元素交换到最后端
Swap(_elements, 0, Size - 1);
Size--;
Heapify(_elements, 0, Size);
return element;
}
public T Peek()
{
return Top;
}
private void HeapInsert(T[] elements, int index)
{
//比较当前节点与父节点之间的大小,从插入位置向上处理。
while (_comparator.Compare(elements[index], elements[(index - 1) / 2]) > 0)
{
Swap(elements, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
public void Clear()
{
Size = 0;
}
private void Heapify(T[] elements, int index, int size)
{
int left = index * 2 + 1;
while (left < size)
{
int comparatorNum = left + 1 < size && _comparator.Compare(elements[left + 1], elements[left]) > 0 ? left + 1 : left;
comparatorNum = _comparator.Compare(elements[comparatorNum], elements[index]) > 0 ? comparatorNum : index;
if (comparatorNum == index) {
break;
}
Swap(elements, comparatorNum, index);
index = comparatorNum;
left = index * 2 + 1;
}
}
// 这里借鉴CSDN博客:
// https://blog.csdn.net/Ilovewolves/article/details/119453154
private void ExpandCapacity() {
Capacity = Mathf.CeilToInt(Capacity * 1.5f);
T[] newElements = new T[Capacity];
for (int i = 0; i < _elements.Length; i++) {
newElements[i] = _elements[i];
}
_elements = newElements;
}
private void Swap(T[] elements, int i, int j){
(elements[i], elements[j]) = (elements[j], elements[i]);
}
}
}
实现思路:上述代码中最核心的就是 HeapInsert 和 Heapify。
private void HeapInsert(T[] elements, int index){
//比较当前节点与父节点之间的大小,从插入位置向上处理。
while (_comparator.Compare(elements[index], elements[(index - 1) / 2]) > 0){
Swap(elements, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
private void Heapify(T[] elements, int index, int size){
int left = index * 2 + 1;
while (left < size){
int comparatorNum = left + 1 < size && _comparator.Compare(elements[left + 1], elements[left]) > 0 ? left + 1 : left;
comparatorNum = _comparator.Compare(elements[comparatorNum], elements[index]) > 0 ? comparatorNum : index;
if (comparatorNum == index) {
break;
}
Swap(elements, comparatorNum, index);
index = comparatorNum;
left = index * 2 + 1;
}
}
优先队列是一种常见的数据结构,最核心的部分是在插入元素时(HeapInsert)和删除元素时(Heapify)的处理。基于上述核心代码,还可以实现堆排序算法,如下:
public void HeapSort(int[] nums){
if(nums == null || nums.Length < 2)
return;
for (int i = 0; i < nums.Length; i++){ //O(N)
HeapInsert(nums, i); //O(logN)
}
var heapSize = nums.Length;
Swap(nums, 0, --heapSize);
while (heapSize > 0){
Heapify(nums, 0, heapSize);
Swap(nums, 0, --heapSize);
}
}
最后,非常感谢您能看到这里,如果该文章对你有所帮助和启发,也欢迎您能点赞支持,如果有任何疑问和优化建议也非常欢迎各位大佬能够在评论区友好交流,一起学习进步。