Files
AdamuSw/src/Pathfinding/BinaryMinHeap{T}.cs
2026-07-14 19:00:35 +03:00

165 lines
4.8 KiB
C#

// <copyright file="BinaryMinHeap{T}.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.Pathfinding;
using System.Runtime.CompilerServices;
/// <summary>
/// A binary min heap which implements <see cref="IPriorityQueue{T}"/>.
/// Objects with the lowest index values appear at the top of the heap, and will be retrieved when calling <see cref="Pop"/>.
/// Please note: This class is not thread safe! Push/Pop should not be executed by two different threads at the same time!.
/// </summary>
/// <remarks>
/// This class contains some optimizations which do not make the code nicer. However,
/// this data structure is THE bottleneck of the pathfinding algorithm, so optimization is worth it.
/// </remarks>
/// <typeparam name="T">The type which should be contained in the heap.</typeparam>
public class BinaryMinHeap<T> : IPriorityQueue<T>
{
private readonly List<T> _innerList = new();
private readonly IComparer<T> _elementComparer;
/// <summary>Reused variable to reduce stack allocations.</summary>
private int _i;
/// <summary>Reused variable to reduce stack allocations.</summary>
private int _parentIndex;
/// <summary>Reused variable to reduce stack allocations.</summary>
private int _left;
/// <summary>Reused variable to reduce stack allocations.</summary>
private int _right;
/// <summary>
/// Initializes a new instance of the <see cref="BinaryMinHeap{T}"/> class.
/// </summary>
public BinaryMinHeap()
{
this._elementComparer = Comparer<T>.Default;
}
/// <summary>
/// Initializes a new instance of the <see cref="BinaryMinHeap{T}"/> class.
/// </summary>
/// <param name="comparer">The comparer.</param>
public BinaryMinHeap(IComparer<T> comparer)
{
this._elementComparer = comparer;
}
/// <summary>
/// Initializes a new instance of the <see cref="BinaryMinHeap{T}"/> class.
/// </summary>
/// <param name="comparer">The comparer.</param>
/// <param name="capacity">The capacity.</param>
public BinaryMinHeap(IComparer<T> comparer, int capacity)
{
this._elementComparer = comparer;
this._innerList.Capacity = capacity;
}
/// <inheritdoc/>
public int Count => this._innerList.Count;
/// <inheritdoc/>
public void Push(T item)
{
this._i = this._innerList.Count;
this._innerList.Add(item);
do
{
if (this._i == 0)
{
break;
}
this._parentIndex = unchecked(this._i - 1) >> 1;
if (this.OnCompareWithElementOfI(this._parentIndex) < 0)
{
this.SwitchElementsParentWithI();
this._i = this._parentIndex;
}
else
{
break;
}
}
while (true);
}
/// <inheritdoc/>
public T Pop()
{
if (this.Count == 0)
{
throw new InvalidOperationException("Heap is empty");
}
var result = this._innerList[0];
this._i = 0;
this._innerList[0] = this._innerList[^1];
this._innerList.RemoveAt(this._innerList.Count - 1);
do
{
this._parentIndex = this._i;
this._left = unchecked((this._i << 1) + 1);
this._right = unchecked((this._i << 1) + 2);
if (this._innerList.Count > this._left && this.OnCompareWithElementOfI(this._left) > 0)
{
this._i = this._left;
}
if (this._innerList.Count > this._right && this.OnCompareWithElementOfI(this._right) > 0)
{
this._i = this._right;
}
if (this._i == this._parentIndex)
{
break;
}
this.SwitchElementsParentWithI();
}
while (true);
return result;
}
/// <summary>
/// Get the smallest object without removing it.
/// </summary>
/// <returns>The smallest object.</returns>
public T Peek()
{
if (this._innerList.Count > 0)
{
return this._innerList[0];
}
throw new InvalidOperationException("Heap is empty");
}
/// <inheritdoc/>
public void Clear()
{
this._innerList.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SwitchElementsParentWithI()
{
T h = this._innerList[this._i];
this._innerList[this._i] = this._innerList[this._parentIndex];
this._innerList[this._parentIndex] = h;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int OnCompareWithElementOfI(int j)
{
return this._elementComparer.Compare(this._innerList[this._i], this._innerList[j]);
}
}