java編寫匈牙利算法解決最優指派問題

匈牙利算法是一種常見的最優指派問題,問題描述如下:

實際中,會遇到這樣的問題,有n項不同的任務,需要n個人分別完成其中的1項,每個人完成任務的時間不一樣。於是就有一個問題,如何分配任務使得花費時間最少。通俗來講,就是n*n矩陣中,選取n個元素,每行每列各有1個元素,使得和最小。如下圖:

指派問題的最優解有這樣一個性質,若從矩陣的一行()各元素中分別減去該行()的最小元素,得到歸約矩陣,其最優解和原矩陣的最優解相同.


匈牙利算法的具體步驟可以參見很多資料:

這裏給出匈牙利算法解決最優指派問題的java實現:

爲了解決可能出現的多解問題,算法加入一個隨機變量,隨機選擇一種最優解當做最終結果。

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+", ");
		}				
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章