匈牙利算法解决指派问题(java版)

18 篇文章 0 订阅

最初想自己写,没成功
参考了一下这个

用的方法是《运筹学(第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)//将元素退栈,返回第二步
		{
			while(top!=-1)
			{
				Main_Node aaa=stack[top--];
				p[aaa.row][aaa.col]=0;
			}
			continue;	
		}
        
		//第四步
        //最小的未被划线的数
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < n; ++j)    
                if (q[i][j] == 0 && min > p[i][j])
                    min = p[i][j];
        
        //更新未被划到的和交点 
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < n; ++j)
                if (q[i][j] == 0)
                    p[i][j] -= min;
                else if (q[i][j] == 2)
                    p[i][j] += min;
        //恢复-1、-2为0   ,   t=n
        for(int i=0;i<n;i++)
        	for(int j=0;j<n;j++)
        		if(p[i][j]<0)   
        			p[i][j]=0;
        t=n;
    }

    //求和及输出 
    int ans = 0;
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < n; ++j)
            if (p[i][j] == -2)
            	{
            	System.out.println("行为"+i+",列为"+j);
                ans += s[i][j];
                }
      System.out.println(ans);          						
											}

	
}	
	
	
	
	


  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值