回溯法求解:工作分配问题

一、问题描述:

设有 n 件工作分配给 n 个人。将工作 i 分配给 j 个人所需的费用为 c_{ij} 。试设计一个算法,为每个人都分配 1 件不同的工作,并使得总费用最小。

输入格式:第一行有 1 个正整数 n (1\leqslant n\leqslant 20)。接下来的 n 行,每行 n 个数,表示费用。

——输入样例:

3

10  2  3

 2  3   4

 3  4   5

——输出样例:

9

 

二、算法解析:

求解该问题的整体思想就是利用回溯法的思想:将所有可能出现的解在逻辑上构造成“树”状,然后利用一些条件为这棵树“剪枝”(排除掉一些解情况);以达到更快求解的效果!

准备工作:

  • 使用一个二维数组 money 记录相应的费用,该数组的横坐标表示第几种工作,纵坐标表示第几个工人

  • 因为每一项工作和每一个工人是一一对应的关系,因此使用一个一维数组 work 表示已经分配工作的工人。(0表示未分配,1表示已经分配。)

  • 使用一个全局变量minPay记录最小的总费用,局部变量nowPay表示计算过程中的局部累计费用

  • 使用变量 t 表示当前已求解问题的深度。(即已经分派到了哪个工作)

具体情形:

  • 情况①:nowPay大于minPay;表示当前累计的费用已经大于已知的最小值,没有继续下去的意义了,费用只会再变大。

  • 情况②:t < n;表示当前分配进展的深度还未到达“叶子结点”(即所有的工作还未分配完);继续进行相应的分配工作。

  • 情况③:t == n;表示当前分配进展的深度达到了“叶子结点”(即所有的工作已经分配完了);此时比较nowPay和minPay的值,minPay取其中的最小值。

 

三、代码实现:

import java.util.Scanner;

public class test {
	public static void main(String[] args) {
		Scanner s = new Scanner(System.in);
		int n = s.nextInt();//用于接收人数
		
		int[][] money = new int[n+1][n+1];//用于接收对应的费用
		for(int i = 1;i<=n;i++)
			for(int j = 1;j<=n;j++)
				money[i][j] = s.nextInt();
		
		s.close();//接收完毕	
		
		int[]work = new int[n+1];//用于表示已分配的工作的人有哪些;0表示未分配,1表示已分配
		
		searchMinPay(0, 0, n, money,work);
		
		System.out.println("最小费用为:"+minPay);
		
	}	
	public static int minPay = 10000;//用于记录最小的费用
	/*
	 * t表示深度层级,
	 * nowPay表示当前的累计费用
	 * n和money,work为辅助数据。
	 */
	public static void searchMinPay(int t,int nowPay,int n,int[][] money,int[]work) {
		if(nowPay>minPay||t>n) {//继续下去已经没有意义
			return;
		}
		
		if(t

回溯法求解:工作分配问题_第1张图片

 

四、优化思路:

①对于minPay的初始值设置进行优化:

在上述代码中,minPay的初值被设置为一个足够大的数,这样使得在实际使用的过程中,minPay是在出现第一个可行解时取其值作为自己的值再利用 if(nowPay>minPay||t>n) 来筛选不用再继续下去的分配;为了提高其筛选效率,我们可以通过将minPay的初值直接赋予一个可行解再进行分配工作,这样 if(nowPay>minPay||t>n)  语句就不用等到出现第一个可行解再起作用了;那么我们如何在不通过运算就可以找到一个可行解呢?

显而易见,按照二维数组 money 的对角线进行工作分配就是一个可行解!

 

②对于分配方式的筛选机制进行了优化:

对于分配的筛选,在上述的代码中我们仅仅依靠的是 if(nowPay>minPay||t>n)  语句来过滤;其实还有一种更为巧妙的筛选机制!

我们将money数组的每一行数据的最小值存储在一个一维数组array中(即完成某一工作的最小花费),当分配到某一个工作时,我们将 nowPay 累计加上剩余未分配工作对应行在一维数组array中存储的最小值,如果得到的 nowPay 值大于 minPay;则说明这条分配线路不会得到更优的解。

在这里你可能会疑惑将money每一行的最小值存入一维数组array中;可能不同工作的最小花费是属于同一个工人,这样不就是违背了我们题干中的要求了吗?

这里的使用并没有错,因为我们设置一维数组array的目的仅仅利用它的值,并不涉及逻辑概念,因为你无论怎么安排工作,最终累加的花费是是大于array中对应元素的累加和。

 

——其实此处对于一位数组array的使用,还有优化的空间!

将一位数组array初始化为二维数组money每一行元素的最小值(注意下标要对应上);然后令array[i]+=array[i+1](倒序,i 从 n 一直取到 1);这样nowPay在累加剩余未分配工作对应行在一维数组array中存储的最小值时,直接加上对应的array[i]元素就可以了!

 

五:优化之后的代码:

为了让优化的效果可视化,我加了一个静态变量count用于记录调用函数的次数,在searchMinPay函数中加上了以下语句:

count++;
System.out.println("第"+count+"次:"+"nowPay:"+nowPay+"    "+"minPay:"+minPay);

原地代码中的

if(nowPay>minPay||t>n)

也改为相应的 

if((nowPay+array[t])>minPay||t>n)

import java.util.Scanner;

public class test {
	public static void main(String[] args) {
		Scanner s = new Scanner(System.in);
		int n = s.nextInt();//用于接收人数
		
		int[][] money = new int[n+1][n+1];//用于接收对应的费用
		for(int i = 1;i<=n;i++)
			for(int j = 1;j<=n;j++)
				money[i][j] = s.nextInt();
		
		s.close();//接收完毕	
		
		int[]work = new int[n+1];//用于表示已分配的工作的人有哪些;0表示未分配,1表示已分配
		
		minPay = 0;//初始化minPay
		for(int i=1;i<=n;i++) 
			minPay+=money[i][i];
		
		for(int i=1;i<=n;i++) {//初始化array数组
			int minvalue = money[i][1];
			for(int j=1;j<=n;j++) {//找出每一行的最小值
				if(money[i][j]=1;i--)
			array[i]+=array[i+1];
						
		searchMinPay(0, 0, n, money,work);
		
		System.out.println("最小费用为:"+minPay);
	}	
	public static int minPay = 10000;//用于记录最小的费用
	public static int[] array = new int[30];//用于记录二维数组money每一行数据的最小值
	/*
	 * t表示深度层级,
	 * nowPay表示当前的累计费用
	 * n和money,work为辅助数据。
	 */
	public static int count = 0;//用于计数
	public static void searchMinPay(int t,int nowPay,int n,int[][] money,int[]work) {
		count++;
		System.out.println("第"+count+"次:"+"nowPay:"+nowPay+"    "+"minPay:"+minPay);
		if((nowPay+array[t])>minPay||t>n) {//继续下去已经没有意义
			return;
		}
		
		if(t
回溯法求解:工作分配问题_第2张图片 右边为优化前,左边为优化后

 

Ending... ....

你可能感兴趣的:(#,算法,算法,回溯法)