FastHungarian 1.0.0
dotnet add package FastHungarian --version 1.0.0
NuGet\Install-Package FastHungarian -Version 1.0.0
<PackageReference Include="FastHungarian" Version="1.0.0" />
<PackageVersion Include="FastHungarian" Version="1.0.0" />
<PackageReference Include="FastHungarian" />
paket add FastHungarian --version 1.0.0
#r "nuget: FastHungarian, 1.0.0"
#:package FastHungarian@1.0.0
#addin nuget:?package=FastHungarian&version=1.0.0
#tool nuget:?package=FastHungarian&version=1.0.0
FastHungarian
Fast assignment problem solver based on modified Kwok's algorithm for maximum weight matching on bipartite graphs. Supports anything from .NET Standard 2.0 to modern .NET 10+ with no dependencies. Solves both minimum cost assignment (Hungarian algorithm) and maximum weight matching problems. ~12-20× faster than classical Hungarian algorithm implementations while maintaining optimal solutions. Extensively tested with comprehensive fuzz testing across thousands of matrix configurations.
Getting Started
Install the package via NuGet:
dotnet add package FastHungarian
Solve assignment problems:
using FastHungarian;
// Cost matrix: assign workers to tasks minimizing total cost
int[,] costs = new int[,]
{
{ 10, 25, 15, 20 }, // Worker 0 costs for tasks 0-3
{ 15, 30, 5, 15 }, // Worker 1 costs for tasks 0-3
{ 20, 5, 10, 25 }, // Worker 2 costs for tasks 0-3
{ 25, 20, 15, 10 } // Worker 3 costs for tasks 0-3
};
int[] assignments = FastHungarian.FindAssignments(costs);
// assignments[i] = task assigned to worker i
// Result: [2, 2, 1, 3] means Worker 0→Task 2, Worker 1→Task 2, etc.
⭐ That's it! The algorithm automatically handles:
- Non-square matrices (different number of workers and tasks)
- Transposition for optimal performance
- Edge retention optimization for large graphs
Advanced Usage
Get Total Cost
Matching result = FastHungarian.FindAssignmentsWithWeight(costs);
Console.WriteLine($"Optimal cost: {result.WeightSum}");
Console.WriteLine($"Assignments: {string.Join(", ", result.LeftPairs)}");
Maximum Weight Matching (Adjacency List API)
For sparse graphs or when you want to maximize weights instead of minimize costs:
// Bipartite graph: L = {0, 1, 2}, R = {0, 1, 2, 3}
// Each list contains (vertex, weight) pairs
var adjacencyList = new List<List<(int vertex, int weight)>>
{
[(0, 10), (1, 5), (2, 3)], // Vertex 0 edges
[(1, 8), (2, 7), (3, 9)], // Vertex 1 edges
[(0, 4), (2, 12), (3, 6)] // Vertex 2 edges
};
Matching result = FastHungarian.MaximumWeightMatching(
leftCount: 3,
rightCount: 4,
adjacencyList
);
Console.WriteLine($"Maximum weight: {result.WeightSum}");
// result.LeftPairs[i] = matched right vertex for left vertex i
// result.RightPairs[j] = matched left vertex for right vertex j
Handling Non-Square Matrices
// More workers than tasks: some workers won't be assigned
int[,] costs = new int[5, 3]; // 5 workers, 3 tasks
int[] assignments = FastHungarian.FindAssignments(costs);
// assignments.Length == 5
// assignments[i] == -1 if worker i is not assigned
// More tasks than workers: all workers assigned, some tasks unassigned
int[,] costs2 = new int[3, 5]; // 3 workers, 5 tasks
int[] assignments2 = FastHungarian.FindAssignments(costs2);
// assignments2.Length == 3
// All assignments2[i] >= 0
Algorithm Details
FastHungarian implements modified Kwok's 2025 algorithm for maximum weight matching with several optimizations:
- Flat adjacency list - Better cache locality vs nested lists
- Edge retention (Theorem 4.2.1) - Reduces edges to O(|L|) per vertex using QuickSelect
- Pre-computed labels - Eliminates redundant scans
- Incremental slack tracking - Avoids repeated minimum calculations
- Ring buffer BFS - Reduces queue overhead
- Span<T> bounds check elimination (.NET 8+) - Zero-overhead array access
- Branchless operations - Better CPU pipelining
Complexity
- Time: O(|L| × |R| × min(|L|, |R|)) worst case, typically much faster in practice
- Space: O(|L| × min(|L|, |R|)) due to edge retention
- Classical Hungarian: O(|L|³) or O(|L|² × |R|)
Performance
Benchmark results on Intel Core i7-8700 (Coffee Lake), baseline is available here:
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|---|
| FastHungarian_Large_100x100 | 402.468 us | 6.9906 us | 10.0257 us | 0.06 | 0.00 | 19.5313 | 6.3477 | - | 122.93 KB | 0.96 |
| Baseline_Large_100x100 | 6,444.749 us | 97.8914 us | 81.7438 us | 1.00 | 0.02 | 15.6250 | - | - | 127.72 KB | 1.00 |
| FastHungarian_Large_150x150 | 791.253 us | 9.7217 us | 8.1180 us | 0.03 | 0.00 | 84.9609 | 84.9609 | 84.9609 | 272.08 KB | 0.95 |
| Baseline_Large_150x150 | 27,457.877 us | 519.1077 us | 710.5605 us | 1.00 | 0.04 | 62.5000 | 62.5000 | 62.5000 | 286.72 KB | 1.00 |
| FastHungarian_Medium_50x50 | 97.723 us | 1.9213 us | 3.5132 us | 0.14 | 0.01 | 5.2490 | 0.3662 | - | 32.41 KB | 1.01 |
| Baseline_Medium_50x50 | 698.737 us | 13.2450 us | 12.3894 us | 1.00 | 0.02 | 4.8828 | - | - | 32.22 KB | 1.00 |
| FastHungarian_Rect_100x50 | 105.358 us | 1.2996 us | 1.1521 us | 0.10 | 0.00 | 10.2539 | 0.9766 | - | 63.81 KB | 0.76 |
| Baseline_Rect_100x50 | 1,106.895 us | 21.9947 us | 33.5883 us | 1.00 | 0.04 | 11.7188 | - | - | 83.98 KB | 1.00 |
| FastHungarian_Rect_50x100 | 86.426 us | 1.4426 us | 1.3494 us | 0.17 | 0.00 | 7.0801 | 0.9766 | - | 43.83 KB | 0.68 |
| Baseline_Rect_50x100 | 522.193 us | 6.3832 us | 5.3303 us | 1.00 | 0.01 | 9.7656 | - | - | 64 KB | 1.00 |
| FastHungarian_Small_10x10 | 1.784 us | 0.0189 us | 0.0158 us | 0.38 | 0.01 | 0.3529 | - | - | 2.17 KB | 1.43 |
| Baseline_Small_10x10 | 4.696 us | 0.0895 us | 0.0793 us | 1.00 | 0.02 | 0.2441 | - | - | 1.52 KB | 1.00 |
| FastHungarian_Sparse_100x100 | 89.620 us | 1.6555 us | 1.4675 us | 0.01 | 0.00 | 19.8975 | 6.5918 | - | 122.93 KB | 0.96 |
| Baseline_Sparse_100x100 | 7,049.420 us | 138.4074 us | 129.4664 us | 1.00 | 0.03 | 15.6250 | - | - | 127.72 KB | 1.00 |
| FastHungarian_WideRange_100x100 | 382.472 us | 6.5613 us | 6.1374 us | 0.05 | 0.00 | 19.5313 | 6.3477 | - | 122.93 KB | 0.96 |
| Baseline_WideRange_100x100 | 8,283.841 us | 151.0171 us | 211.7049 us | 1.00 | 0.04 | 15.6250 | - | - | 127.72 KB | 1.00 |
Memory usage: ~5% less allocation than Baseline Hungarian algorithm.
Sparse matrices (90% high cost, 10% low cost): 60× faster than vanilla for 100×100.
Full benchmark results and reproduction available in the benchmark project.
Testing
FastHungarian is thoroughly tested with 11 comprehensive fuzz test suites covering:
- ✅ Square matrices (2×2 to 200×200)
- ✅ Non-square matrices (all aspect ratios)
- ✅ Extreme shapes (1×N, N×1, 10:1 ratios)
- ✅ Edge case patterns (diagonal, sparse, bimodal, outliers)
- ✅ Degenerate cases (1×1, single row/column, near-overflow)
- ✅ Total: 350+ test iterations, thousands of matrix configurations
All tests verify correctness against the proven Hungarian algorithm implementation from the HungarianAlgorithm NuGet package.
Limitations
- Integer weights: Currently supports
intweights. For floating-point weights, scale to integers.
The algorithm handles:
- ✅ Non-square matrices (automatic transposition)
- ✅ Unbalanced assignments (more workers than tasks or vice versa)
- ✅ Zero costs and identical values
- ✅ Large cost ranges (tested up to
int.MaxValue / 2)
Contributing
If you are looking to add new functionality, please open an issue first to verify your intent is aligned with the scope of the project. The library is covered by comprehensive fuzz tests with over 350 test iterations. Please run them against your work before proposing changes:
dotnet test
When reporting issues, providing a minimal reproduction with the specific cost matrix that fails greatly reduces turnaround time.
References
- Kwok, J. T. (2025). An Improved Algorithm for Maximum Weight Matching in Bipartite Graphs
- Based on the classical Hungarian algorithm (Kuhn-Munkres algorithm)
License
This library is licensed under the MIT license. 💜
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 265 | 11/15/2025 |