1. 程式人生 > >匈牙利演算法解決指派問題(java版)

匈牙利演算法解決指派問題(java版)

最初想自己寫,沒成功
參考了一下這個

用的方法是《運籌學(第4版)》,《運籌學》教材編寫組編,清華大學出版社
6.5指派問題的匈牙利演算法
上面是用c++寫的,我改成了java,但不是完全一樣,應該是比原作者時間複雜度低。

用-1標記φ,-2標記◎(獨立零元素)。
在第二步(4)中需要隨機選,第三步發現選錯了需要返回的,所以可以用一個棧儲存 二(4)之後的東西,發現選錯了恢復為0。

有一個問題沒有處理好,就是二(4)選了以後,到第三步錯了,課本上說的是回到二(4)另行試探,就是不能重複原來的那種選法了。而我每次都是隨機選,隨機的話有一定概率一直選到那個錯的元素,這樣就結束不了。

還有一點,在第二步裡面刪除同行同列可以遍歷,但是當元素個數多的時候遍歷太慢,可以用一下十字連結串列,有心的人可以實現一下

下面的Main_Node就是記錄下某行某列的節點


package fd;

public class Main_Node {
int row;
int col;
public Main_Node(int row, int col) {
	super();
	this.row = row;
	this.col = col;
}
}

看程式可以從main開始看,縷清結構

//記錄每行每列0的個數
	public static void countZero(int
[]row,int[]col) { for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) if (p[i][j] == 0) { row[i]++; col[j]++; } } }
//畫最少的線覆蓋所有0元素 
	public static int  drawLine()
	{    
	    for
(int i=0;i<n;i++) for(int j=0;j<n;j++) q[i][j]=0; for (int i = 0; i < n; ++i) x[i] = 1; for (int j = 0; j < n; ++j) y[j] = 0; //row 對所有不含獨立0元素的行打勾! for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (p[i][j] == -2) { x[i] = 0; break; } } } boolean is = true; while (is) //迴圈直到沒有勾可以打 { is = false; //col 對打勾的行中含φ元素的未打勾的列打勾 for (int i = 0; i < n; ++i) { if (x[i] == 1) { for (int j = 0; j < n; ++j) { if (p[i][j] == -1 && y[j] == 0) { y[j] = 1; is = true; } } } } //row 對打勾的列中含◎的未打勾的行打勾 for (int j = 0; j < n; ++j) { if (y[j] == 1) { for (int i = 0; i < n; ++i) { if (p[i][j] ==-2&& x[i] == 0) { x[i] = 1; is = true; } } } } } //沒有打勾的行和有打勾的列畫線,這就是覆蓋所有0元素的最少直線數 int line = 0; for (int i = 0; i < n; ++i) { if (x[i] == 0) { for (int j = 0; j < n; ++j) q[i][j]++; line++; } if (y[i] == 1) { for (int j = 0; j < n; ++j) q[j][i]++; line++; } } return line; }
//更新行列的0且進棧
	public static void refresh1(int index,int index2,int[]row,int[]col)
	{
		for (int j = 0; j < n; ++j)
	        if (p[index][j] == 0)//若該行還有0且沒被劃掉才更新 
	            {
	        	row[index]--;
	        	col[j]--;
	        	p[index][j]=-1;
	        	stack[++top]=new Main_Node(index,j);
	            }
	    for (int i = 0; i < n; ++i)
	        if (p[i][index2] == 0)
	            {
	        	row[i]--;
	        	col[index2]--;
	            p[i][index2]=-1;
	            stack[++top]=new Main_Node(i,index2);
	            }
	}
	
	//更新行列的0不進棧
	public static void refresh2(int index,int index2,int[]row,int[]col)
	{
		for (int j = 0; j < n; ++j)
	        if (p[index][j] == 0)//若該行還有0且沒被劃掉才更新 
	            {
	        	row[index]--;
	        	col[j]--;
	        	p[index][j]=-1;
	            }
	    for (int i = 0; i < n; ++i)
	        if (p[i][index2] == 0)
	            {
	        	row[i]--;
	        	col[index2]--;
	            p[i][index2]=-1;
	            }
	}
//第二步、
	//找獨立0元素個數 
	/*1.找含0最少的那一行/列    2.劃掉,更新該行/列0元素所在位置的row[],col[]
	  3.直到所有0被劃線退出      
	  4.need為false說明只做了前兩步,need為true說明做了第四步(第四步是猜的,所以要進棧,如果第三步發現猜錯了,出棧)
	 */
	public static int find()
	{
		int zero=0;     //獨立0元素的個數
	    int []row=new int[n]; 
		int []col=new int[n];//行列0元素個數 
	    
		countZero(row,col);
		
		
	    
	    while (true)
	    {	
	    	for(int i=0;i<n;i++)
		    {
	    		if (row[i] == 0)
	                row[i] = Integer.MAX_VALUE;
	            if (col[i] == 0)
	                col[i] = Integer.MAX_VALUE;
		    }
	    	
	    	
	        boolean stop = true;
	        int row_min=Arrays.stream(row).min().getAsInt();
	        int col_min= Arrays.stream(col).min().getAsInt();
	        if(row_min==Integer.MAX_VALUE)  break;
	        if (row_min<=col_min)    //行有最少0元素 
	        {	if(row_min>1)
	        	{
	        		need_stack=true;
	        	}
	            //找含0最少的那一行 
	            int tmp = Integer.MAX_VALUE; 
	            int index = -1;
	            for (int i = 0; i < n; ++i)
	            {
	                if (tmp > row[i])
	                {
	                	tmp = row[i];
	                	index = i;
	                }
	                    
	            }
	            
	            /*找該行任意一個沒被劃掉的0元素(獨立0元素),任意找一個*/ 
	            int index2 = -1;            //該行獨立0元素的列值
	            int count=(int)(Math.random()*row[index]);//隨機選哪一個0
	            int k=0; 
	            for (int i = 0; i < n; ++i)
	                if (p[index][i] == 0)
	                {	
	                	if(k==count)
	                    {
	                		index2 = i;
	                		stop = false;            //找到獨立0元素則繼續迴圈 
		                    zero++;                //獨立0元素的個數 
		                    break;
	                    }
	                	k++;
	                    
	                }
	            
	            //找不到獨立0元素了 
	            if (stop)    
	                break;
	                
	            //標記 
	            row[index]--;
	            col[index2]--;    
	            p[index][index2] =-2;//獨立0元素
	            if(need_stack)
	            	{
	            	stack[++top]=new Main_Node(index,index2);
	            	refresh1(index,index2,row,col);//更新其他0且都要進棧
	            	}
	            else 
	            	refresh2(index,index2,row,col);
	            
	           
	        }
	        else       //列有最少0元素 
	        {	
	        	if(col_min>1)
	        	{
	        		need_stack=true;
	        	}
	        	
	            int tmp = Integer.MAX_VALUE;
	            int index = -1;
	            for (int i = 0; i < n; ++i)
	            {
	                if (tmp > col[i])
	                    {
	                	tmp = col[i];
	                    index = i;
	                    }
	            }
	            
	            int index2 = -1;
	            int count=(int)(Math.random()*col[index]);//隨機選哪一個0
	            int k=0; 
	            for (int i = 0; i < n; ++i)
	                if (p[i][index] == 0)
	                {	
	                	if(k==count)
	                    {
	                		index2 = i;
	                		stop = false;            //找到獨立0元素則繼續迴圈 
		                    zero++;                //獨立0元素的個數 
		                    break;
	                    }
	                	k++;
	                }
       
	            if (stop)
	                break;
	                
	            row[index2]--;
	            col[index]--;
	            p[index2][index] =-2;
	            if(need_stack)
            	{
            	stack[++top]=new Main_Node(index2,index);
            	refresh1(index2,index,row,col);//更新其他0且都要進棧
            	}
	            else 
            	refresh2(index2,index,row,col);
	           
	                    
	        }
	    }
	    
	    return zero;
	}
package fd;

import java.util.Arrays;
import java.util.Scanner;

public class Main {
	//define Max 100
	public static int max=100;
	public static int n;                    //維數 
	public static int [][]s=new int[max][max];        //原始矩陣 
	public static int [][]p=new int[max][max];        //歸約矩陣 
	public static int [][]q=new int[max][max];        //0:未被畫線 1:畫了1次 2: 畫了2次(交點) 
	
	public static int []x=new int[max];
	public static int []y=new int[max];       //畫線時是否被打勾,1是0不是 
	public static boolean need_stack=false;//當為true時,需要進棧
	public static Main_Node stack[]=new Main_Node[10000];
	public static int top=-1;
	//在規約矩陣裡面0是0   
	//-1φ,-2是◎(獨立零元素)
	//三做完後如果返回可能要回退棧內的元素
	public static void main(String[] args) {
	//第零步:輸入矩陣
	System.out.println("請輸入一個<=100的階數:");
	Scanner in =new Scanner(System.in);
	n=in.nextInt();
	System.out.println("矩陣元素:");
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			{s[i][j]=in.nextInt();
			 p[i][j]=s[i][j];}
	in.close();
	//第一步:變出零來
	for(int i=0;i<n;i++)
	{
		int min=p[i][0];
		for(int j=1;j<n;j++)
			min=Math.min(min, p[i][j]);
		if(min!=0)
		{
			for(int j=0;j<n;j++)
				p[i][j]-=min;
		}
	}
	
	for(int j=0;j<n;j++)
	{
		int min=p[0][j];
		for(int i=1;i<n;i++)
			min=Math.min(min, p[i][j]);
		if(min!=0)
		{
			for(int i=0;i<n;i++)
				p[i][j]-=min;
		}
	}
	//第二步find()
	int t=n;//t是本次find()要找的個數
	//在第二步裡是n,第三步回退的是top+1
	//第四步還是n
	while(find()<t)
	{	
		t=top+1;//進棧的元素個數,也是出棧的元素個數
		//第三步drawLine
		if(drawLine()==n)