指派问题的匈牙利解法

  1. 理解指派问题的匈牙利解法的原理。
  2. 编写程序求解如下问题:
    某商业公司计划开办5家新商店,公司决定由5家建筑公司分别承建。已知建筑公司Ai 对新商店Bj 的建造费用报价如下表所示。问:商业公司应对5家建筑公司如何分配建造任务,使得总建造费用最小?

    指派问题的匈牙利解法_第1张图片

  3. 源代码:

//指派问题的匈牙利解法:
package intGh;
public class zhipaiWT {
    public static void assign(int[][] m) {
        int N = m.length;
        // 行规约
        for (int i = 0; i < N; i++) {
            int min = Integer.MAX_VALUE;
            for (int j = 0; j < N; j++) {
                if (m[i][j] < min)
                    min = m[i][j];
            }
            for (int j = 0; j < N; j++)
                m[i][j] -= min;
        }
        // 列规约
        for (int j = 0; j < N; j++) {
            int min = Integer.MAX_VALUE;
            for (int i = 0; i < N; i++) {
                if (m[i][j] < min)
                    min = m[i][j];
            }
            if (min == 0)
                continue;
            for (int i = 0; i < N; i++)
                m[i][j] -= min;
        }
        print_m(m);
        // 进行试分配
        while (true) {
            boolean zeroExist = true;
            while (zeroExist) {
                zeroExist = false;
                if (rAssign(m))
                    zeroExist = true;
                if (cAssign(m))
                    zeroExist = true;
                print_m(m);
            }
            // 判断是否达到最优分配
            if (isOptimal(m))
                break;
            // 变换矩阵
            updM(m);
            // 将0元素恢复
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++)
                    if (m[i][j] < 0)
                        m[i][j] = 0;
            }
            print_m(m);
        }
    }
    public static void updM(int[][] m) {
        int N = m.length;
        // 记录行、列是否打钩
        boolean[] rowIsChecked = new boolean[N];
        boolean[] colIsChecked = new boolean[N];
        // 给没有被圈的行打钩
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                if (m[i][j] == -1) {
                    rowIsChecked[i] = false;
                    break;
                } else {
                    rowIsChecked[i] = true;
                }
            }
        }
        boolean isChecked = true;
        while (isChecked) {
            isChecked = false;
            // 对所有打钩行的0元素所在列打钩
            for (int i = 0; i < N; i++) {
                if (rowIsChecked[i]) {
                    for (int j = 0; j < N; j++) {
                        if (m[i][j] == -2 && !colIsChecked[j]) {
                            colIsChecked[j] = true;
                            isChecked = true;
                        }
                    }
                }
            }
            // 对打钩列上的独立零元素行打钩
            for (int j = 0; j < N; j++) {
                if (colIsChecked[j]) {
                    for (int i = 0; i < N; i++) {
                        if (m[i][j] == -1 && !rowIsChecked[i]) {
                            rowIsChecked[i] = true;
                            isChecked = true;
                        }
                    }
                }
            }
        }
        // 寻找盖零线以外最小的数
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < N; i++) {
            if (rowIsChecked[i]) {
                for (int j = 0; j < N; j++) {
                    if (!colIsChecked[j]) {
                        if (m[i][j] < min)
                            min = m[i][j];
                    }
                }
            }
        }
        // 打钩各行减去min
        for (int i = 0; i < N; i++) {
            if (rowIsChecked[i]) {
                for (int j = 0; j < N; j++) {
                    if (m[i][j] > 0)
                        m[i][j] -= min;
                }
            }
        }
        // 打钩各列加上min
        for (int j = 0; j < N; j++) {
            if (colIsChecked[j]) {
                for (int i = 0; i < N; i++) {
                    if (m[i][j] > 0)
                        m[i][j] += min;
                }
            }
        }
    }
    // 统计被圈起来的0数量,判断是否找到最优解
    public static boolean isOptimal(int[][] m) {
        int count = 0;
        for (int i = 0; i < m.length; i++) {
            for (int j = 0; j < m.length; j++)
                if (m[i][j] == -1)
                    count++;
        }
        return count == m.length;
    }
    // 寻找只有一个0元素的行,将其标记为独立0元素(-1),对其所在列的0元素画叉(-2)
    // 若存在独立0元素返回true
    public static boolean rAssign(int[][] m) {
        boolean zeroExist = false;
        int N = m.length;
        // 寻找只有一个0元素的行(列)
        for (int i = 0; i < N; i++) {
            int zeroCount = 0;
            int colIndex = -1;
            for (int j = 0; j < N; j++) {
                if (m[i][j] == 0) {
                    zeroCount++;
                    colIndex = j;
                    zeroExist = true;
                }
            }
            // 将独立0元素标记为-1(被圈起来),对应的列上的零标记为-2(被划去)
            if (zeroCount == 1) {
                m[i][colIndex] = -1;
                for (int k = 0; k < N; k++) {
                    if (k == i)
                        continue;
                    else if (m[k][colIndex] == 0)
                        m[k][colIndex] = -2;
                }
            } else if (zeroCount == 2) {// 如果存在2组解,随机选择其一标记
                if (Math.random() > 0.5) {
                    m[i][colIndex] = -1;
                    for (int k = 0; k < N; k++) {
                        if (k == i)
                            continue;
                        else if (m[k][colIndex] == 0)
                            m[k][colIndex] = -2;
                    }
                    for (int j = 0; j < N; j++) {
                        if (j == colIndex)
                            continue;
                        else if (m[i][j] == 0)
                            m[i][j] = -2;
                    }
                }
            }
        }
        return zeroExist;
    }
    // 寻找只有一个0元素的列,将其标记为独立0元素(-1),对其所在行的0元素画叉(-2)
    // 若存在独立0元素返回true
    public static boolean cAssign(int[][] m) {
        boolean zeroExist = false;
        int N = m.length;
        for (int j = 0; j < N; j++) {
            int zeroCount = 0;
            int rowIndex = -1;
            for (int i = 0; i < N; i++) {
                if (m[i][j] == 0) {
                    zeroCount++;
                    rowIndex = i;
                    zeroExist = true;
                }
            }
            if (zeroCount == 1) {
                m[rowIndex][j] = -1;
                for (int k = 0; k < N; k++) {
                    if (k == j)
                        continue;
                    else if (m[rowIndex][k] == 0)
                        m[rowIndex][k] = -2;
                }
            }
        }
        return zeroExist;
    }
    static int cost[] = new int[5];
    public static void printResult(int[][] m, int[][] M) {
        System.out.print("\n最优指派为:\n");
        for (int i = 0; i < m.length; i++) {
            for (int j = 0; j < m.length; j++)
                if (m[i][j] == -1) {
                    int ii = i + 1;
                    int jj = j + 1;
                    System.out.print("公司A" + ii + "承建工程B" + jj + "\n");
                    totalCost += M[i][j];
                    cost[i] = M[i][j];
                }
        }
    }
    static int num = 1;
    public static void print_m(int[][] m) {
        System.out.printf("\n第%d次变换后系数矩阵变为:\n", num);
        for (int i = 0; i < m.length; i++) {
            for (int j = 0; j < m.length; j++)
                System.out.print(m[i][j] + "\t");
            System.out.println();
        }
        num++;
    }
    static int totalCost = 0;
    // 主函数
    public static void main(String[] args) {
        int[][] m = new int[][] { { 4, 8, 7, 15, 12 }, { 7, 9, 17, 14, 10 }, { 6, 9, 12, 8, 7 }, { 6, 7, 14, 6, 10 },
                { 6, 9, 12, 10, 6 } };
        int[][] M = new int[5][5];
        for (int i = 0; i < 5; i++)
            for (int j = 0; j < 5; j++)
                M[i][j] = m[i][j];
        assign(m);
        printResult(m, M);
        System.out.println("\n总建造费用为:" + cost[0] + "+" + cost[1] + "+" + cost[2] + "+" + cost[3] + "+" + cost[4] + "="
                + totalCost);
    }
}

运行结果:
(1) 系数矩阵变化的过程:
指派问题的匈牙利解法_第2张图片
指派问题的匈牙利解法_第3张图片
(2) 该问题的最优指派方案:
指派问题的匈牙利解法_第4张图片
(3) 该最优指派方案总费用为:
最优指派方案总费用

你可能感兴趣的:(指派问题的匈牙利解法)