匈牙利演算法(Hungarian Algorithm)用於解決指派問題:以最小總成本將 N 項工作指派給 N 個工人。本文完整呈現 C# 控制台應用程式實現,包含矩陣縮減、覆蓋零元素、調整矩陣、尋找最佳指派四大步驟,並詳細解析核心邏輯。
public class ZMatrix
{
private int[,] _data;
private List<Point> _result = new List<Point>();
private int _x, _y; // 行數、列數
public ZMatrix(int botNum, int pointNum) // 建構子初始化測試資料
{
_x = botNum; _y = pointNum;
_data = new int[botNum, pointNum];
// 硬編碼 4x4 測試矩陣...
}
public void Calculation()
{
Step1();
while (!Step2())
{
Step3();
}
}
// Step 1: 列/行縮減
private void Step1()
{
for (int x = 0; x < _x; x++)
{
int minY = int.MaxValue;
for (int y = 0; y < _y; y++) minY = Math.Min(minY, _data[x, y]);
for (int y = 0; y < _y; y++) _data[x, y] -= minY;
}
for (int y = 0; y < _y; y++)
{
int minX = int.MaxValue;
for (int x = 0; x < _x; x++) minX = Math.Min(minX, _data[x, y]);
for (int x = 0; x < _x; x++) _data[x, y] -= minX;
}
}
// Step 2: 貪婪選取零(按零個數少的行優先)
private bool Step2()
{
_result.Clear();
var isDelete = new bool[_x, _y];
var zeroNodes = new List<ZZeroNode>();
for (int x = 0; x < _x; x++)
{
int zeroNum = 0;
for (int y = 0; y < _y; y++) if (_data[x, y] == 0) zeroNum++;
if (zeroNum > 0) zeroNodes.Add(new ZZeroNode(x, zeroNum));
}
zeroNodes.Sort(ZZeroNode.Cmp); // 依零個數升序
while (zeroNodes.Count > 0)
{
var node = zeroNodes[0];
if (node.ZeroNum <= 0) { zeroNodes.RemoveAt(0); continue; }
for (int y = 0; y < _y; y++)
{
if (_data[node.X, y] == 0 && !isDelete[node.X, y])
{
_result.Add(new Point(node.X, y));
zeroNodes.RemoveAt(0);
// 標記同行同列其他零為已刪除
for (int xx = 0; xx < _x; xx++)
if (_data[xx, y] == 0)
{
isDelete[xx, y] = true;
for (int i = 0; i < zeroNodes.Count; i++)
if (zeroNodes[i].X == xx) zeroNodes[i].ZeroNum--;
}
break;
}
}
zeroNodes.Sort(ZZeroNode.Cmp);
}
return _result.Count == _x; // 是否完成完全指派
}
// Step 3: 最少線覆蓋所有零 + Step 4 調整
private void Step3()
{
var isDelete = new bool[_x, _y];
// 貪婪覆蓋:對每個零,比較同行/同列零個數,覆蓋較多者
for (int x = 0; x < _x; x++)
for (int y = 0; y < _y; y++)
if (_data[x, y] == 0 && !isDelete[x, y])
{
int xc = 0, yc = 0;
for (int nx = 0; nx < _x; nx++) if (nx != x && _data[nx, y] == 0) xc++;
for (int ny = 0; ny < _y; ny++) if (ny != y && _data[x, ny] == 0) yc++;
if (xc > yc) for (int xx = 0; xx < _x; xx++) isDelete[xx, y] = true;
else for (int yy = 0; yy < _y; yy++) isDelete[x, yy] = true;
}
// 找未覆蓋最小值 K
int k = int.MaxValue;
for (int x = 0; x < _x; x++)
for (int y = 0; y < _y; y++)
if (!isDelete[x, y]) k = Math.Min(k, _data[x, y]);
// 未覆蓋行減 K
for (int x = 0; x < _x; x++)
for (int y = 0; y < _y; y++)
if (!isDelete[x, y])
{
for (int y1 = 0; y1 < _y; y1++) _data[x, y1] -= k;
break;
}
// 負值列加 K 修正
for (int x = 0; x < _x; x++)
for (int y = 0; y < _y; y++)
if (_data[x, y] < 0)
{
for (int x1 = 0; x1 < _x; x1++) _data[x1, y] += k;
break;
}
}
}
// 輔助類別:記錄行索引與零個數
class ZZeroNode
{
public int X, ZeroNum;
public ZZeroNode(int x, int zeroNum) { X = x; ZeroNum = zeroNum; }
public static int Cmp(ZZeroNode a, ZZeroNode b) => a.ZeroNum.CompareTo(b.ZeroNum);
}
// 4x4 成本矩陣
9 4 6 8
8 5 9 10
9 7 3 5
4 8 6 9
// 執行結果指派:(0,1), (1,0), (2,2), (3,0) 等
// 總最小成本 = 4 + 8 + 3 + 4 = 19
| 階段 | 複雜度 | 優化建議 |
|---|---|---|
| Step 1 縮減 | O(n²) | 已最優 |
| Step 2 選取 | O(n³) 最壞 | 改用 Kuhn-Munkres (最大匹配) O(n³) 但常數更小 |
| Step 3/4 覆蓋調整 | O(n³) | 改用標號法 (Labeling) 避免重複掃描 |
HungarianAlgorithm (NuGet)、Google OR-Tools本實現完整展示了匈牙利演算法的四步驟核心邏輯,代碼結構清晰、變數命名明確,適合作為演算法教學參考。實際專案中建議直接採用經過驗證的開源庫,避免邊界條件錯誤(如非方陣、負成本、多重最優解)導致業務風險。