设有 n 件工作分配给 n 个人。将工作 i 分配给 j 个人所需的费用为 。试设计一个算法,为每个人都分配 1 件不同的工作,并使得总费用最小。
输入格式:第一行有 1 个正整数 n ()。接下来的 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
①对于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
Ending... ....