匈牙利算法的Java实现
熟悉指派问题并想用匈牙利算法的同学们可以参考一下。代码方面我只实现了平衡指派问题,不平衡指派问题的话希望有缘人自己试一试吧。
思路方面建议参考清华大学出版社的运筹学(第四版)。
下面展示 算法的代码
。
package HungarianAlgorithm;
import java.util.Arrays;
public class Ha {
public static void main(String[] args) {
double[][] a = { { 12, 7, 9, 7, 9 }, { 8, 9, 6, 6, 6 }, { 7, 17, 12, 14, 9 }, { 15, 14, 6, 6, 10 },
{ 4, 10, 7, 10, 9 } };
// double[][] a = { { 0, 4, 2, 2, 3 }, { 7, 0, 0, 0, 2 }, { 4, 0, 0, 0, 7 }, {
// 6, 0, 0, 0, 4 },
// { 2, 1, 1, 4, 0 } };
// double[][] a = { { 26, 38, 41, 52, 27 }, { 25, 33, 44, 59, 21 }, { 20, 30,
// 47, 56, 25 },
// { 22, 31, 45, 53, 20 } };
// double[][] m = { { 2, 15, 13, 4 }, { 10, 4, 14, 15 }, { 9, 14, 16, 13 }, { 7,
// 8, 11, 9 } };
// double[][] m = { { 26, 38, 41, 52, 27 }, { 25, 33, 44, 59, 21 }, { 20, 30,
// 47, 56, 25 }, { 22, 31, 45, 53, 20 },
// { 0, 0, 0, 0, 0 } };
// double[][] m = new double[][] { { 15, 18, 21, 24 }, { 19, 23, 22, 18 }, { 26,
// 17, 16, 19 },
// { 19, 21, 23, 17 } };
// 将人数小于任务数的不平衡指派问题转化成平衡指派问题
//这部分有一小点是我想要写不平衡指派转化平衡指派的,没完成但不影响平衡指派方面的运行
int L = a.length;
int S = a[1].length;
if (L <= S) {
double[][] b = new double[S - L][S];
for (int i = 0; i < S - L; i++) {
for (int j = 0; j < S; j++) {
b[i][j] = 0;
}
}
double m[][] = Arrays.copyOf(a, a.length + b.length);
System.arraycopy(b, 0, m, a.length, b.length);
// 保留原矩阵
int N = m.length;
double[][] c = new double[N][N];
c = copy(m, c);
// 进行指派操作
guiyue(m);
tryAppoint(m);
// 输出最小值
double finalAnswer = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (m[i][j] == -1) {
finalAnswer += c[i][j];
}
}
}
System.out.println();
System.out.println("min=" + finalAnswer);
}
}
// 第一步:规约
public static void guiyue(double[][] m) {
int N = m.length;
// 行规约
for (int i = 0; i < N; i++) {
double min = Double.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++) {
double min = Double.MAX_VALUE;
for (int i = 0; i < N; i++) {
if (m[i][j] < min) {
min = m[i][j];
}
}
for (int i = 0; i < N; i++) {
m[i][j] -= min;
}
}
// 输出规约矩阵
printM(m);
}
// 第二步:试指派
public static void tryAppoint(double[][] m) {
int N = m.length;
int zeroNumber = 0;
int zeroNumber1 = -1;
// 当行或列零元素为1的时候,进行画圈、画叉操作,直到再也不能画圈
while (zeroNumber1 != zeroNumber) {
zeroNumber1 = zeroNumber;
rowsAppoint(m);
colsAppoint(m);
zeroNumber = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (m[i][j] == 0) {
zeroNumber += 1;
}
}
}
}
// 从零元素最少的行(但不为0)开始画圈,直到再也没有零元素(为false即零不存在)
do {
rowsAppoint2(m);
} while (rowsAppoint2(m));
// 输出进行试指派之后的矩阵
System.out.println("试指派之后的矩阵");
printM(m);
if (judge(m)) {
printResult(m);
} else {
drawZeroLine(m);
}
}
// 第三步:画零盖线
public static void drawZeroLine(double[][] 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;
}
}
}
// 先给每个列都标记为叉
for (int j = 0; j < N; j++) {
colIsChecked[j] = false;
}
int trueCount = 0;
int trueCount1 = -1;
// 当行或列零元素为1的时候,进行画圈、画叉操作,直到再也不能画圈
while (trueCount1 != trueCount) {
trueCount1 = trueCount;
// 给打勾行中含叉的列打勾
for (int i = 0; i < N; i++) {
if (rowIsChecked[i]) {
for (int j = 0; j < N; j++) {
if (m[i][j] == -2) {
colIsChecked[j] = 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] = true;
}
}
}
}
// 统计打勾行与列的总个数
trueCount = 0;
for (int i = 0; i < N; i++) {
if (rowIsChecked[i] == true) {
trueCount += 1;
}
}
for (int j = 0; j < N; j++) {
if (colIsChecked[j] == true) {
trueCount += 1;
}
}
}
// 验证性程序,可删除(和lineCount无关的代码都可以删掉)
// 统计覆盖零元素的直线数
System.out.println("---------------");
int lineCount = 0;
for (int i = 0; i < N; i++) {
System.out.println("rowIsChecked[" + (i + 1) + "]=" + rowIsChecked[i]);
if (rowIsChecked[i] == false) {
lineCount += 1;
}
}
for (int j = 0; j < N; j++) {
System.out.println("colIsChecked[" + (j + 1) + "]=" + colIsChecked[j]);
if (colIsChecked[j] == true) {
lineCount += 1;
}
}
System.out.println("覆盖所有0元素的最小直线数为" + lineCount);
// 验证性程序截至
// 判断下行步骤
if (lineCount < N) {
System.out.println("执行步骤四");
// 恢复矩阵
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (m[i][j] < 0) {
m[i][j] = 0;
}
}
}
System.out.println("恢复矩阵");
printM(m);
// 第四步:更新矩阵
// 找到划线外的最小值
double minValue = Double.MAX_VALUE;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (rowIsChecked[i] == true && colIsChecked[j] == false) {
if (m[i][j] < minValue) {
minValue = m[i][j];
}
}
}
}
// 打勾行减去该最小值,打勾列加上该最小值
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (rowIsChecked[i] == true) {
m[i][j] -= minValue;
}
if (colIsChecked[j] == true) {
m[i][j] += minValue;
}
}
}
System.out.println("更新矩阵");
printM(m);
// 试指派
tryAppoint(m);
} else if (lineCount == N) {
System.out.println("返回步骤二");
// 恢复矩阵
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (m[i][j] < 0) {
m[i][j] = 0;
}
}
}
tryAppoint(m);
}
}
public static void rowsAppoint(double[][] m) {
int N = m.length;
for (int i = 0; i < N; i++) {
int rowCount = 0;
int colIndex = Integer.MIN_VALUE;
for (int j = 0; j < N; j++) {
if (m[i][j] == 0) {
rowCount += 1;
colIndex = j;
}
}
// 给只有1个零元素的行中的零元素画圈,该元素所在的列其他0元素画叉
if (rowCount == 1) {
m[i][colIndex] = -1;
for (int i1 = 0; i1 < N; i1++) {
if (i1 == i) {
continue;
} else if (m[i1][colIndex] == 0) {
m[i1][colIndex] = -2;
}
}
}
}
}
public static boolean rowsAppoint2(double[][] m) {
boolean zeroExist = false;
int N = m.length;
int rowZeroNumber = Integer.MAX_VALUE;
// 标记零最少的行中的零的个数
for (int i = 0; i < N; i++) {
int rowZeroCount = 0;
for (int j = 0; j < N; j++) {
if (m[i][j] == 0) {
rowZeroCount += 1;
zeroExist = true;
}
}
if (rowZeroCount < rowZeroNumber && rowZeroCount != 0) {
rowZeroNumber = rowZeroCount;
}
}
// 给最少零元素的行中的随机零元素画圈,其他零元素画叉,该零元素所在的列其他0元素画叉
for (int i = 0; i < N; i++) {
int rowCount = 0;
int colIndex = Integer.MAX_VALUE;
for (int j = 0; j < N; j++) {
if (m[i][j] == 0) {
rowCount += 1;
colIndex = j;
zeroExist = true;
}
}
if (rowCount == rowZeroNumber) {
if (Math.random() > 0.95) {
for (int i1 = 0; i1 < N; i1++) {
if (m[i1][colIndex] == 0) {
m[i1][colIndex] = -2;
}
}
for (int j1 = 0; j1 < N; j1++) {
if (m[i][j1] == 0) {
m[i][j1] = -2;
}
}
m[i][colIndex] = -1;
break;
}
}
}
return zeroExist;
}
// 给只有1个零元素的列中的零元素画圈,该元素所在的行其他0元素画叉
public static void colsAppoint(double[][] m) {
int N = m.length;
for (int j = 0; j < N; j++) {
int colCount = 0;
int rowIndex = Integer.MIN_VALUE;
for (int i = 0; i < N; i++) {
if (m[i][j] == 0) {
colCount += 1;
rowIndex = i;
}
}
// 给只有1个零元素的行中的零元素画圈,该元素所在的列其他0元素画叉
if (colCount == 1) {
m[rowIndex][j] = -1;
for (int j1 = 0; j1 < N; j1++) {
if (j1 == j) {
continue;
} else if (m[rowIndex][j1] == 0) {
m[rowIndex][j1] = -2;
}
}
}
}
}
public static boolean judge(double[][] m) {
int count = 0;
int N = m.length;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (m[i][j] == -1) {
count += 1;
}
}
}
return count == N;
}
public static double[][] copy(double[][] m, double[][] a) {
int N = m.length;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
a[i][j] = m[i][j];
}
}
return (a);
}
public static void printM(double[][] m) {
int N = m.length;
System.out.println("---------------");
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
System.out.print(m[i][j] + " ");
}
System.out.println();
}
}
public static void printResult(double[][] m) {
int N = m.length;
double finalAnswer = 0;
System.out.println("-----Result------");
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (m[i][j] == -1) {
System.out.print((i + 1) + "--" + (j + 1) + " ");
}
}
}
}
}