ACM中的工作分配问题是一个典型的回溯问题,利用回溯思想能很准确地得到问题的解。下面就这个问题好好分析下。
问题描述:
设有n件工作分配给n个人。为第i个人分配工作j所需的费用为c[i][j] 。试设计一个算法,计算最佳工作分配方案,为每一个人都分配1 件不同的工作,并使总费用达到最小。
解题思路:
由于每个人都必须分配到工作,在这里可以建一个二维数组c[i][j],用以表示i号工人完成j号工作所需的费用。给定一个循环,从第1个工人开始循环分配工作,直到所有工人都分配到。为第i个工人分配工作时,再循环检查每个工作是否已被分配,没有则分配给i号工人,否则检查下一个工作。可以用一个一维数组x[j]来表示第j 号工作是否被分配,未分配则x[j]=0,否则x[j]=1。利用回溯思想,在工人循环结束后回到上一工人,取消此次分配的工作,而去分配下一工作直到可以分配为止。这样,一直回溯到第1个工人后,就能得到所有的可行解。在检查工作分配时,其实就是判断取得可行解时的二维数组的一下标都不相同,二下标同样不相同。
样例分析:
给定3件工作,i号工人完成j号工作的费用如下:
10 2 3
2 3 4
3 4 5
假定一个变量count表示工作费用总和,初始为0,变量i表示第i号工人,初始为1。n表示总的工作量,这里是取3。c[i][j]表示i号工人完成j号工作的费用,x[j]表示j号工作是否被分配。算法如下:
[cpp]
view plain
copy
- void work(int i,int count){
- if(i>n)
- return ;
- for(int j=1;j<=n;j++)
- if(x[j] == 0){
- x[j] = 1;
- work(i+1,count+c[i][j]);
- x[j] = 0;
- }
- }
那么在这里,用回溯法的思想就是,首先分配的工作是:
10:c[1][1] 3:c[2][2] 5:c[3][3] count=18;
此时,所有工人分配结束,然后回溯到第2个工人重新分配:
10:c[1][1] 4:c[2][3] 4:c[3][2] count=18;
第2个工人已经回溯到n,再回溯到第1个工人重新分配:
2:c[1][2] 2:c[2][1] 5:c[3][3] count=9;
回溯到第2个工人,重新分配:
2:c[1][2] 4:c[2][3] 3:c[3][1] count=9;
再次回溯到第1个工人,重新分配:
3:c[1][3] 2:c[2][1] 4:c[3][2] count=9;
回溯到第2个工人,重新分配:
3:c[1][3] 3:c[2][2] 3:c[3][1] count=9;
这样,就得到了所有的可行解。而我们是要得到最少的费用,即可行解中和最小的一个,故需要再定义一个全局变量cost表示最终的总费用,初始cost为c[i][i]之和,即对角线费用相加。在所有工人分配完工作时,比较count与cost的大小,如果count小于cost,证明在回溯时找到了一个最优解,此时就把count赋给cost。
到这里,整个算法差不多也快结束了,已经能得到最终结果了。但考虑到算法的复杂度,这里还有一个剪枝优化的工作可以做。就是在每次计算局部费用变量count的值时,如果判断count已经大于cost,就没必要再往下分配了,因为这时得到的解必然不是最优解。
[cpp]
view plain
copy
- #include<iostream>
- using namespace std;
- int n,cost=0;
- int x[100],c[100][100];
- void work(int i,int count){
- if(i>n && count<cost){
- cost = count;
- return ;
- }
- if(count<cost)
- for(int j=1;j<=n;j++)
- if(x[j] == 0){
- x[j] = 1;
- work(i+1,count+c[i][j]);
- x[j] = 0;
- }
- }
- int main(){
- cin>>n;
- for(int i=1;i<=n;i++){
- for(int j=1;j<=n;j++){
- cin>>c[i][j];
- x[j] = 0;
- }
- cost+=c[i][i];
- }
- work(1,0);
- cout<<cost<<endl;
- system(“pause”);
- return 0;
/************************************************************************
** 功能: 将n个作业分配给n个人完成,使得总花费时间最短(回溯法)
************************************************************************/
#include "stdafx.h"
#include <iostream>
#include<fstream>
#include<string>
using namespace std;
//-----------------------------------------------------------------------
#define N 64 //N表示最大作业数和工人数
int c[N][N]; //c[i][j] 表示工人i执行作业j所用的时间(下标从1开始)
unsigned int mincost = 65535; //设置的费用初始值
int isAssigned[N]; //记录作业是否已被分配给工人,1表示已分配,0为未分配
int currSolution[N]; //当前作业分配情况:工人i 执行作业currSolution[i]
int bestSolution[N]; //最优作业分配情况: 工人i执行作业bestSolution[i]
int njob; //作业、工人数
//-----------------------------------------------------------------------
/************************************************************************
** 功能:初始化操作
** 输入参数:工人、作业数njob
** 花费矩阵c[n][n]
** 同时初始化isAssigned, currSolution, bestSolution数组
************************************************************************/
void initData(int& njob, int c[][N],int* isAssigned, int* currSolution, int* bestSolution)
{
string str("input_assign04_01.dat");
ifstream fin(str);
fin>>njob;
for(int i = 1; i <= njob; i++)
for (int j = 1; j <= njob; j++)
fin >> c[i][j];
cout<< "花费矩阵为:(每一行代表一个工人,每一列代表一个作业)" << endl;
for(int i = 1; i <= njob; i++)
{
for (int j = 1; j <= njob; j++)
cout << c[i][j] << " ";
cout << endl;
}
cout<< "工人数和作业数均为:" << njob << endl;
cout<< "--------------------------------------------------" << endl;
//初始化数组
for(int k =0; k <= njob; k++)
{
isAssigned[k] = 0;
currSolution[k] = 0;
bestSolution[k] = 0;
}
}
/************************************************************************
** 功能:回溯法分配作业
** 参数:k工人编号
** cost为分配当前作业后的花费
************************************************************************/
void assignJob(int k,unsigned int cost)
{
int i;
if(k > njob)
{
mincost = cost;
for(i=1; i<=njob; i++)
bestSolution[i] = currSolution[i];
}
else
{
for(i=1;i<=njob;i++)
{
if(isAssigned[i]==0 && cost+c[k][i] < mincost)
{
isAssigned[i] = 1; currSolution[k] = i;
assignJob(k+1,cost+c[k][i]);
isAssigned[i] = 0; currSolution[k] = 0;
}
}
}
}
/************************************************************************
** 功能:输出结果
** 参数:minCost最小花费
** njob工人作业数
** bestSolution分配方案
************************************************************************/
void printResult(int minCost,int njob, int* bestSolution)
{
cout<<"最小总费用为: "<<mincost<<endl;
cout<<"分配方案为:"<<endl;
for(int i=1; i<= njob;i++)
cout<< "工人: "<< i <<" -> 分配作业: "<< bestSolution[i] <<endl;
ofstream fout("output_assign04_01.dat");
fout<< "最小总费用为: " <<mincost<<endl;
for(int j=1; j<= njob;j++)
fout<< "工人: "<< j <<" -> 分配作业: "<< bestSolution[j] <<endl;
}
void main()
{
//从文件读入工人作业数njob和花费矩阵c
//同时初始化isAssigned, currSolution, bestSolution数组
initData(njob, c, isAssigned, currSolution, bestSolution);
assignJob(1,0);
printResult(mincost, njob,bestSolution);//输出
system("pause");
}