匈牙利演算法解決指派問題(java版)
阿新 • • 發佈:2018-12-09
最初想自己寫,沒成功
參考了一下這個
用的方法是《運籌學(第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)