首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 實現匈牙利演算法完整代碼解析:指派問題最佳化求解

     編輯:關於C語言

前言

匈牙利演算法(Hungarian Algorithm)用於解決指派問題:以最小總成本將 N 項工作指派給 N 個工人。本文完整呈現 C# 控制台應用程式實現,包含矩陣縮減、覆蓋零元素、調整矩陣、尋找最佳指派四大步驟,並詳細解析核心邏輯。

演算法四步驟流程

  1. Step 1 - 矩陣縮減:各行減去最小值 → 各列減去最小值,產生至少一個零的矩陣
  2. Step 2 - 尋找獨立零集合:嘗試選取 N 個互不相同行列的零(完全指派)
  3. Step 3 - 覆蓋所有零:用最少水平/垂直線覆蓋所有零,若線數 < N 則進入 Step 4
  4. Step 4 - 調整矩陣:找出未被覆蓋的最小值 K,未覆蓋行減 K、覆蓋列加 K,回 Step 2

核心類別 ZMatrix 完整實現

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
  • 大規模問題 (n > 1000) 改用網路流/線性規劃求解器
  • 矩陣稀疏時採用稀疏矩陣儲存優化記憶體

總結

本實現完整展示了匈牙利演算法的四步驟核心邏輯,代碼結構清晰、變數命名明確,適合作為演算法教學參考。實際專案中建議直接採用經過驗證的開源庫,避免邊界條件錯誤(如非方陣、負成本、多重最優解)導致業務風險。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved