Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pairing Heap #42

Merged
merged 2 commits into from
Mar 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ Ephemeral data structure for priority queues.
- **O(log(N))** extract minimum (like `System.Collections.Generic.SortedSet<T>`)
- data can contain duplicates (unlike `System.Collections.Generic.SortedSet<T>`)

### [Pairing Heap](https://en.wikipedia.org/wiki/Pairing_heap)
Ephemeral data structure for adressable priority queues.
- **O(1)** insert
- **O(1)** get minimum
- **O(log(N)) amortized** extract minimum
- **O(log(N)) amortized** delete
- **O(log(N)) amortized** decrease key
- **O(1)** merge

### [Dynamic Array](https://en.wikipedia.org/wiki/Dynamic_array)
Ephemeral data structure for dynamic arrays, used internally.
- like `System.Collections.Generic.List<T>`
Expand Down
225 changes: 225 additions & 0 deletions src/Cornucopia.DataStructures.Tests/PairingHeapTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
using System.Collections.Generic;
using System.Linq;

using NUnit.Framework;

namespace Cornucopia.DataStructures
{
[TestFixture]
public class PairingHeapTests
{
[Test]
public void Ctor_DefaultComparer_CountIsZero()
{
var heap = new PairingHeap<int>(Comparer<int>.Default);
Assert.That(heap.Count(), Is.Zero);
}

[Test]
public void Count_Empty_IsZero()
{
var heap = new PairingHeap<int>();
Assert.That(heap.Count(), Is.Zero);
}

[Test]
public void Insert_Empty_IsNotEmpty()
{
var heap = new PairingHeap<int>();
heap.Insert(0);
Assert.That(heap.IsEmpty, Is.False);
}

[TestCase(5)]
[TestCase(42)]
public void Insert_MultipleTimesIntoEmpty_CountIsCorrect(int size)
{
var heap = new PairingHeap<int>();
for (var i = 0; i < size; i++)
{
heap.Insert(0);
}

Assert.That(heap.Count(), Is.EqualTo(size));
}

[Test]
public void Minimum_Empty_Throws()
{
var heap = new PairingHeap<int>();
Assert.That(() => heap.Minimum, Throws.InvalidOperationException);
}

[Test]
public void Minimum_SingleElement_ReturnsElement()
{
var heap = new PairingHeap<int>();
heap.Insert(42);
Assert.That(heap.Minimum, Is.EqualTo(42));
}

[TestCase(1, 2, 3)]
[TestCase(2, 1, 3)]
[TestCase(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)]
public void Minimum_ManyElements_IsCorrect(params int[] values)
{
var heap = new PairingHeap<int>();
foreach (var value in values)
{
heap.Insert(value);
}

Assert.That(heap.Minimum, Is.EqualTo(values.Min()));
}

[Test]
public void ExtractMinimum_Empty_Throws()
{
var heap = new PairingHeap<int>();
Assert.That(() => heap.ExtractMinimum(), Throws.InvalidOperationException);
}

[Test]
public void ExtractMinimum_SingleElement_ReturnsElement()
{
var heap = new PairingHeap<int>();
heap.Insert(42);
Assert.That(heap.ExtractMinimum(), Is.EqualTo(42));
}

[Test]
public void ExtractMinimum_SingleElement_RemovesElement()
{
var heap = new PairingHeap<int>();
heap.Insert(0);
heap.ExtractMinimum();
Assert.That(heap.IsEmpty, Is.True);
Assert.That(() => heap.Minimum, Throws.InvalidOperationException);
}

[TestCase(1, 2, 3)]
[TestCase(2, 1, 3)]
[TestCase(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)]
[TestCase(5, 3, 4, 11, 8, 15)]
public void ExtractMinimum_ManyElements_CanSortSequence(params int[] values)
{
var heap = new PairingHeap<int>();
foreach (var value in values)
{
heap.Insert(value);
}

var sorted = new List<int>();
while (!heap.IsEmpty)
{
sorted.Add(heap.ExtractMinimum());
}

Assert.That(sorted, Is.EqualTo(values.OrderBy(x => x).ToList()));
}

[Test]
public void Remove_ManyElements_DecreasesCount()
{
var heap = new PairingHeap<int>();
for (var i = 0; i < 15; i++)
{
heap.Insert(i);
}

var pointer = heap.Insert(5);
heap.Remove(pointer);

Assert.That(heap.Count(), Is.EqualTo(15));
}

[Test]
public void Remove_MiddleElement_DecreasesCount()
{
var heap = new PairingHeap<int>();
heap.Insert(2);
var pointer = heap.Insert(1);
heap.Insert(0);
heap.Remove(pointer);
Assert.That(heap.Count(), Is.EqualTo(2));
}

[TestCase(5, 3, 4, 11, 8, 15)]
public void Remove_AllElements_IsEmpty(params int[] values)
{
var heap = new PairingHeap<int>();
var pointers = values.Select(heap.Insert).ToList();
pointers.ForEach(heap.Remove);
Assert.That(heap.IsEmpty, Is.True);
}

[Test]
public void GetItem_SingleElement_ReturnsValue()
{
var heap = new PairingHeap<int>();
var pointer = heap.Insert(42);
Assert.That(heap[pointer], Is.EqualTo(42));
}

[Test]
public void SetItem_Decrease_ExtractsAllElementsCorrectly()
{
var heap = new PairingHeap<int>();
heap.Insert(2);
var pointer = heap.Insert(1);
heap.Insert(0);
heap[pointer] = -1;
var elements = ExtractAll(heap).ToArray();
Assert.That(elements, Is.EqualTo(new[] { -1, 0, 2 }));
}

[Test]
public void SetItem_Increase_ExtractsAllElementsCorrectly()
{
var heap = new PairingHeap<int>();
heap.Insert(2);
var pointer = heap.Insert(1);
heap.Insert(0);
heap[pointer] = 3;
var elements = ExtractAll(heap).ToArray();
Assert.That(elements, Is.EqualTo(new[] { 0, 2, 3 }));
}

[Test]
public void Merge_WithEmpty_CountUnchanged()
{
var heap = new PairingHeap<int>();
heap.Insert(0);
heap.Merge(new());
Assert.That(heap.Count(), Is.EqualTo(1));
}

[Test]
public void Merge_WithNonEmpty_CountChanged()
{
var heap = new PairingHeap<int>();
var other = new PairingHeap<int>();
other.Insert(0);
heap.Merge(other);
Assert.That(heap.Count(), Is.EqualTo(1));
}

[Test]
public void Merge_WithNonEmpty_ClearsArgument()
{
var heap = new PairingHeap<int>();
var other = new PairingHeap<int>();
other.Insert(0);
heap.Merge(other);
Assert.That(other.IsEmpty, Is.True);
}

private static IEnumerable<int> ExtractAll(PairingHeap<int> heap)
{
while (heap.TryExtractMinimum(out var min))
{
yield return min;
}
}
}
}
2 changes: 1 addition & 1 deletion src/Cornucopia.DataStructures/BinaryHeap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Cornucopia.DataStructures
{
/// <summary>
/// A min-heap
/// A min-heap based on the simple binary heap structure.
/// </summary>
/// <typeparam name="T">The type of elements to store in the heap.</typeparam>
public class BinaryHeap<T>
Expand Down
16 changes: 16 additions & 0 deletions src/Cornucopia.DataStructures/OptimizedChildSiblingTreeNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Cornucopia.DataStructures
{
internal class OptimizedChildSiblingTreeNode<T>
{
public OptimizedChildSiblingTreeNode(T value)
{
this.Value = value;
}

public OptimizedChildSiblingTreeNode<T>? LeftSiblingOrParent { get; set; }
public OptimizedChildSiblingTreeNode<T>? RightSibling { get; set; }
public OptimizedChildSiblingTreeNode<T>? FirstChild { get; set; }

public T Value { get; set; }
}
}
Loading