匈牙利算法是一種常見的最優指派問題,問題描述如下:
實際中,會遇到這樣的問題,有n項不同的任務,需要n個人分別完成其中的1項,每個人完成任務的時間不一樣。於是就有一個問題,如何分配任務使得花費時間最少。通俗來講,就是n*n矩陣中,選取n個元素,每行每列各有1個元素,使得和最小。如下圖:
指派問題的最優解有這樣一個性質,若從矩陣的一行(列)各元素中分別減去該行(列)的最小元素,得到歸約矩陣,其最優解和原矩陣的最優解相同.
匈牙利算法的具體步驟可以參見很多資料:
package test;
/**
* 匈牙利算法解決最優指派問題
* @author Hao
*
*/
public class Hungary {
public static void appoint(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];
}
if(min == 0)
continue;
for(int i = 0;i < N;i ++)
m[i][j] -=min;
}
printM(m);
//進行試分配
while(true){
boolean zeroExist = true;
while(zeroExist){
zeroExist = false;
if(rowAppoint(m))
zeroExist = true;
if(colAppoint(m))
zeroExist = true;
printM(m);
}
//判斷是否達到最優分配
if(isOptimal(m))
break;
//變換矩陣
updataM(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;
}
printM(m);
}
}
public static void updataM(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;
}
}
}
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;
}
}
}
}
}
//尋找蓋零線以外最小的數
double min = Double.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(double[][] 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 rowAppoint(double[][] 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.95){
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 colAppoint(double[][] 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;
}
public static void main(String[] args) {
double[][] m = new double[][]{{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}};
appoint(m);
printResult(m);
}
public static void printM(double[][] m){
System.out.println("---------------");
for(int i = 0;i < m.length;i ++){
for(int j = 0;j < m.length;j ++)
System.out.print(m[i][j] + " ");
System.out.println();
}
}
public static void printResult(double[][] m){
System.out.println("-----Result------");
for(int i = 0;i < m.length;i ++){
for(int j = 0;j < m.length;j ++)
if(m[i][j] == -1)
System.out.print(i+"--"+j+", ");
}
}
}