解题思路:
1.确定上下界,下界down是每一行最小值,上界up使用贪心法计算,所以范围为[down,up]
2.限界函数lb=已分配任务员工花费总时间+未分配任务员工最小花费时间
3.使用广度优先遍历,计算lb,如果lb超过上界,则丢弃,如果在范围内,先将第一层数据符合条件的放入优先队列
4.循环队列,每次从优先队列中选取最小的lb进行广度优先遍历
1)如果取出的结点为叶子结点,与min_result比较得到在最终的min_result
2)如果取出的结点为非叶子结点,则访问该结点的孩子节点并计算其lb,如果lb超过上界,则丢弃,如果在范围内,先将孩子节点放入优先队列
循环直到计算完所有值。
运行过程说明:
输入:你选择的文件编号
输出:输出计算最小花费的过程以及最小花费时间
伪码描述:
arrangeWork()
for j<-0 to n do//使用广度优先遍历,先将第一层加入优先队列,让优先队列不为空(计算lb,如果lb超过上界,则丢弃,如果在范围内,放入优先队列中)。
node.i<-0
node.j<-j
node.v<-this.cost[0][j]
getLb(node)
if this.down<=node.lb&&node.lb<=this.up
then this.PT.add(node)
}
while !this.PT.isEmpty() do//每次从中选取最小的lb进行广度优先遍历,直到计算完所有值,从二维数组第二行开始
node<-this.PT.poll()
if node.i==this.n-1//即到达叶子结点,记录最小值
then if this.min_result==0
then this.min_result<-node.lb
this.min_node<-node
else do
if node.lb<this.min_result
then this.min_result<-node.lb
this.min_node<-node
else do//不是叶子结点,将该结点的子结点加入优先队列
k<-node.i+1//层数
father=node
for j<-0 to n do//标记全部初始化为0
this.flag[j]<-0
this.flag[node.j]<-1//标记该任务已经被分配
while node.father!=null do//记录该结点的父结点已经被分配了的任务
this.flag[node.father.j]<-1
node<-node.father
for j<-0 to n do//创建孩子结点加入优先队列
if this.flag[j]==0
then child<-new Node()
child.i<-k
child.j<-j
child.v<-this.cost[k][j]
child.father<-father
getLb(child)
if this.down<=child.lb&&child.lb<=this.up
then this.PT.add(child)
算法的复杂度分析:O(n!)
算法设计:
用广度优先方式来实现分支限界搜索排列树
使用优先队列存放满足lb目标函数的结点,将其从小到大排序,循环队列,每次从优先队列中选取最小的lb进行广度优先遍历
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Scanner;
class Node{
int i;//记录人员编号方便计算其余下行的最小值
int j;//记录任务编号,方便标记该任务是否被分配
int v;//记录当前分配任务员工花费的时间
int lb;//记录当前分配任务员工目标函数lb=已分配任务员工花费总时间+未分配任务员工最小花费时间
Node father=null;//方便找到该结点的父结点,用于计算上层结点花费的时间
}
public class assign05 {
public int n;//n表示任务数或者人数
public int flag[];//用flag[j] 标记第j个工作是否己分配工,1表示已经分配了工作,0表示还没有分配工作
public int cost[][];//记录不同人完成不同任务所需时间
public int down;//下界:每行最小值之和
public int up;//上界:使用贪心算法,不同行不同列取值
public int min[];//记录每行最小值
public PriorityQueue<Node> PT;//待处理的结点表
public int min_result;//记录最小的花费时间
public Node min_node;
public static void main(String[] args) throws IOException {
assign05 object=new assign05();
while (true) {//循环访问不同文件内容
object.min_result=0;
object.down= 0;
object.up= 0;
object.min_node=null;
object.readFile();
object.min=new int[object.n];//初始化min数组
for (int k=0;k<object.n;k++) {
object.min[k] = (int) Double.POSITIVE_INFINITY;
}
object.getDown();
System.out.println("该二维数组的下界为:"+object.down);
object.getUp();
System.out.println("该二维数组的上界为:"+object.up);
object.PT=new PriorityQueue<Node>(new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
return o1.lb-o2.lb;
}
});
object.arrangeWork();
for (int k=0;k<object.n;k++){
int i=object.min_node.i;
int j=object.min_node.j;
System.out.println("要想花费最少时间应该选择第"+(i+1)+"人,完成第"+(j+1)+"份任务");
object.min_node=object.min_node.father;
}
System.out.println("将" + object.n + "份作业分配给" + object.n + "个人完成,总花费时间的最短为:" + object.min_result);
}
}
/*读文件,将文件内容写到二维数组中*/
public void readFile() throws IOException {
System.out.println("共6组文件(1-6),输入你选择的文件编号:");
Scanner sc=new Scanner(System.in);
int fileNum=sc.nextInt();//获得文件编号
BufferedReader bufferedReader=new BufferedReader(new FileReader("./src/input_assign05_0"+fileNum+".txt"));
this.n=Integer.parseInt(bufferedReader.readLine());//n为n份工作或n个人
this.cost=new int[this.n][this.n];
for (int i=0;i<this.n;i++){
for (int j=0;j<this.n;j++){
this.cost[i][j]= 0;//初始化
}
}
for (int i=0;i<this.n;i++){
String str=bufferedReader.readLine();
for (int j=0;j<this.n;j++){
this.cost[i][j]= Integer.parseInt(str.split(" ")[j]);//将读取的信息存到cost二维数组中
}
}
this.flag=new int[this.n];
for (int j=0;j<this.n;j++){//标记该工作是否被分配,初始化为0为被分配,1表示被分配了不能再分配给他人使用
this.flag[j]=0;
}
}
/*获得down值*/
public void getDown(){//获得初始下界
for (int i=0;i<this.n;i++){
for (int j=0;j<this.n;j++){
if (this.cost[i][j]<this.min[i]){
this.min[i]=this.cost[i][j];//min数组中记录了每行的最小值
}
}
this.down+=this.min[i];//down为每行最小值之和
}
}
/*获得up值*/
public void getUp(){//获得初始上界
int loc=-1;
for (int i=0;i<this.n;i++){
int min=(int) Double.POSITIVE_INFINITY;//记录每行最小值,初始化为正无穷
for (int j=0;j<this.n;j++){
if (cost[i][j]<min&&this.flag[j]==0){
min=cost[i][j];
loc=j;
}
}
this.flag[loc]=1;
this.up+=min;
}
for (int j=0;j<this.n;j++){//重新将该数组标记置为0,标记该工作是否被分配,0为未被分配,1表示被分配了不能再分配给他人使用
this.flag[j]=0;
}
}
/*计算结点的lb目标函数*/
public void getLb(Node node){//lb=已分配任务员工花费总时间+未分配任务员工最小花费时间
int sum=0;//该结点上层结点花费时间总和
Node father=node.father;
while (father!=null){//已分配任务标记为1
int i=father.i;
int j=father.j;
sum+=this.cost[i][j];
father=father.father;
}
int remain_min=0;//记录未分配任务员工最小花费时间
for (int i=node.i+1;i<this.n;i++){
remain_min+=this.min[i];//记录未分配任务员工最小花费时间=剩余每行最小值之和,min数组中存放每行最小值
}
node.lb=sum+node.v+remain_min;
}
/*进行任务分配*/
public void arrangeWork(){
for (int j=0;j<this.n;j++){//使用广度优先遍历,先将第一层加入优先队列,让优先队列不为空(计算lb,如果lb超过上界,则丢弃,如果在范围内,放入优先队列中)。
Node node=new Node();
node.i=0;
node.j=j;
node.v=this.cost[0][j];
getLb(node);
if (this.down<=node.lb&&node.lb<=this.up) {
this.PT.add(node);
}
}
while(!this.PT.isEmpty()){//每次从中选取最小的lb进行广度优先遍历,直到计算完所有值,从二维数组第二行开始
Node node=this.PT.poll();
if (node.i==this.n-1){//即到达叶子结点,记录最小值
if (this.min_result==0){
this.min_result=node.lb;
this.min_node=node;
}else {
if (node.lb<this.min_result){
this.min_result=node.lb;
this.min_node=node;
}
}
} else {//不是叶子结点,将该结点的子结点加入优先队列
int k=node.i+1;//层数
Node father=node;
for (int j=0;j<this.n;j++){//标记全部初始化为0
this.flag[j]=0;
}
this.flag[node.j]=1;//标记该任务已经被分配
while (node.father!=null){//记录该结点的父结点已经被分配了的任务
this.flag[node.father.j]=1;
node=node.father;
}
for (int j=0;j<this.n;j++){//创建孩子结点加入优先队列
if (this.flag[j]==0){
Node child=new Node();
child.i=k;
child.j=j;
child.v=this.cost[k][j];
child.father=father;
getLb(child);
if (this.down<=child.lb&&child.lb<=this.up) {
this.PT.add(child);
}
}
}
}
}
}
}