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号工作是否被分配。算法如下:
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,就没必要再往下分配了,因为这时得到的解必然不是最优解。
测试代码:
#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; }
//工作分配问题 /* 3 10 2 3 2 3 4 3 4 5 */ #include <iostream> #include <algorithm> using namespace std; int table[21][21],n,best,r[21]; int compute(int k) { int temp=0,i; for(i=1;i<=k;i++) temp+=table[i][r[i]]; return temp; } void search(int k) { if(k==n) { int temp=compute(n); if(temp<best) best=temp; return; } for(int i=k;i<=n;i++) { swap(r[i],r[k]); if(compute(k) < best) search(k+1); swap(r[i],r[k]); } } int main() { int i,j; while(cin>>n,n) { for(i=1;i<=n;i++) for(j=1;j<=n;j++) cin>>table[i][j]; for(i=1;i<=n;i++) r[i]=i; best=INT_MAX; search(1); cout<<best<<endl; } return 0; }
运行过程说明: jobAssignBacktrack.exe 输入源数据文件路径+文件名 在同目录下可以查看输出文件output.txt ★问题描述 有n份作业分配给n个人去完成,每人完成一份作业。假定第i个人完成第j份作业需要花费cij时间,cij>0,1≦i,j≦n。试设计一个回溯算法,将n份作业分配给n个人完成,使得总花费时间最短。 ★格式输入 由文件input.txt给出输入数据。第一行有1 个正整数n (1≤n≤100)。 接下来的n行,每行n个数,表示工作费用。 ★格式输出 将计算出的结果输出到文件output.txt。 ★示例输入输出 5 50 87 62 56 92 43 22 98 57 36 1 5 97 96 43 58 62 27 73 27 60 71 38 71 95
144 工人: 1 执行任务: 4 工人: 2 执行任务: 2 工人: 3 执行任务: 1 工人: 4 执行任务: 5 工人: 5 执行任务: 3 ★算法设计(包括算法设计过程中相关内容的说明、数据结构的选择、 算法详细描述及算法分析): ●1.设计说明: 算法中使用job[i]记录作业i是否已被分配,若被分配则置1,不必再搜索其分支;、 assign(int k,unsigned int cost) cost用来记录当前作业分配的开销,作为参数传递给下一层递归调用。 ●2.算法设计: ●2.1数据结构: #define N 100 //N表示最大任务数和工人数 int c[N][N]; //c[i][j] 表示工人i执行作业j所用的时间 unsigned int mincost = 65535; //设置的初始值,大于可能的费用 int task[N], //当前作业分配情况: 工人i执行task[i] bestSolution[N], //最优作业分配情况: 工人i执行作业bestSolution[i] job[N]; //记录作业是否已被分配给工人,1表示已分配,0为分配 int njob; //实际工作、工人数 主要操作: void input(int& njob, int c[][N]); //读入工人、作业数njob,开销矩阵c[][N] //初始化task,bestSolution,job,读入工人、作业数njob,开销矩阵c[][N] void initData(int* task, int* bestSolution, int* job, int& njob, int c[][N]); void assign(int k,unsigned int cost); //为工人k分配作业,cost:当前作业分配后的花费 //输出结果:mincost、作业分配情况 void outputResult(int minCost,int njob, int* bestSolution); ●2.2算法描述: void assign(int k,unsigned int cost) //为工人k分配作业,cost:当前作业分配后的花费 { int i; if(k > njob && cost < mincost) //搜索至叶节点,判断是否更优 { mincost = cost; for(i=1; i<=njob; i++) bestSolution[i] = task[i]; } else { for(i=1;i<=njob;i++) { if(job[i]==0 && cost+c[k][i])//约束函数,job[i]==0则为被分配 { job[i] = 1; task[k] = i; assign(k+1,cost+c[k][i]); job[i] = 0; task[k] = 0; } } } } ●2.3算法分析 本算法的最坏时间复杂度为:O(n!) 最坏空间复杂度为:O(n)
[c-sharp] view plain copy print ?
- //=======================================================================
- //jobAssign.cpp
- //将n份作业分配给n个人完成,使得总花费时间最短,回溯法
- //by leo
- //5.13.2011
- //=======================================================================
- #include <iostream>
- #include<fstream>
- #include<string>
- using namespace std;
- //-----------------------------------------------------------------------
- #define N 100 //N表示最大任务数和工人数
- int c[N][N]; //c[i][j] 表示工人i执行作业j所用的时间
- unsigned int mincost = 65535; //设置的初始值,大于可能的费用
- int task[N], //当前作业分配情况:工人i 执行task[i]
- bestSolution[N], //最优作业分配情况: 工人i执行作业bestSolution[i]
- job[N]; //记录作业是否已被分配给工人,1表示已分配,0为分配
- int njob; //实际工作、工人数
- //-----------------------------------------------------------------------
- void input(int& njob, int c[][N]); //读入工人、作业数njob,开销矩阵c[][N]
- //初始化task,bestSolution,job,读入工人、作业数njob,开销矩阵c[][N]
- void initData(int* task, int* bestSolution, int* job, int& njob, int c[][N]);
- void assign(int k,unsigned int cost); //为工人k分配作业,cost:当前作业分配后的花费
- //输出结果:mincost、作业分配情况
- void outputResult(int minCost,int njob, int* bestSolution);
- //-----------------------------------------------------------------------
- void main()
- {
- initData(task, bestSolution, job, njob, c);
- assign(1,0); //从任务1开始分配
- outputResult(mincost, njob,bestSolution);
- system("pause");
- }
- //-----------------------------------------------------------------------
- //读入工人、作业数njob,开销矩阵c[][N]
- //-----------------------------------------------------------------------
- void input(int& njob, int c[][N])
- {
- string str;
- cout << "Please input the input file name"
- << "(input_assign04_00.txt~input_assign04_05.txt):/n";
- cin >> str;
- ifstream fin(str.c_str());
- if(!fin)
- {
- cout << "no such file,please check the file name!" <<endl;
- exit(0);
- }
- fin>>njob;
- for(int i = 1; i <= njob; i++)
- for (int j = 1; j <= njob; j++)
- fin >> c[i][j];
- }
- //-----------------------------------------------------------------------
- //初始化task,bestSolution,job,读入工人、作业数njob,开销矩阵c[][N]
- //-----------------------------------------------------------------------
- void initData(int* task, int* bestSolution, int* job, int& njob, int c[][N])
- {
- input(njob,c);
- for(int k =0; k <= njob; k++)
- {
- job[k] = 0;
- task[k] = 0;
- bestSolution[k] = 0;
- }
- }
- //-----------------------------------------------------------------------
- //为工人k分配作业,cost:当前作业分配后的花费
- //-----------------------------------------------------------------------
- void assign(int k,unsigned int cost)
- {
- int i;
- if(k > njob && cost < mincost)
- {
- mincost = cost;
- for(i=1; i<=njob; i++)
- bestSolution[i] = task[i];
- }
- else
- {
- for(i=1;i<=njob;i++)
- {
- if(job[i]==0 && cost+c[k][i])
- {
- job[i] = 1; task[k] = i;
- assign(k+1,cost+c[k][i]);
- job[i] = 0; task[k] = 0;
- }
- }
- }
- }
- //-----------------------------------------------------------------------
- //输出结果:mincost、作业分配情况
- //-----------------------------------------------------------------------
- void outputResult(int minCost,int njob, int* bestSolution)
- {
- cout<<"最小总费用:"<<mincost<<endl;
- for(int i=1; i<= njob;i++)
- cout<< "工人: "<< i <<" 执行任务: "<< bestSolution[i] <<endl;
- ofstream fout("output.txt");
- fout<<mincost<<endl;
- for(int j=1; j<= njob;j++)
- fout<< "工人: "<< j <<" 执行任务: "<< bestSolution[j] <<endl;
- }
- //=======================================================================
以下的代码稍作改变,用于c[i][j]表示作业i由工人j完成的情况
[c-sharp] view plain copy print ?
- //=======================================================================
- //jobAssign.cpp
- //将n份作业分配给n个人完成,使得总花费时间最短,回溯法
- //by leo
- //5.13.2011
- //=======================================================================
- #include <iostream>
- #include<fstream>
- #include<string>
- using namespace std;
- //-----------------------------------------------------------------------
- #define N 100 //N表示最大任务数和工人数
- int c[N][N]; //c[i][j] 表示作业i由人j执行所做的时间
- unsigned int mincost = 65535; //设置的初始值,大于可能的费用
- int task[N], //当前作业分配情况:i由工人task[i]执行
- bestSolution[N], //最优作业分配情况: i由工人bestSolution[i]执行
- worker[N]; //记录工人是否已被分配作业
- int njob; //实际工作、工人数
- //-----------------------------------------------------------------------
- //读入工人、作业数njob,开销矩阵c[][N]
- //-----------------------------------------------------------------------
- void input(int& njob, int c[][N])
- {
- string str;
- cout << "Please input the input file name"
- << "(input_assign02_00.txt~input_assign02_01.txt):/n";
- cin >> str;
- ifstream fin(str.c_str());
- if(!fin)
- {
- cout << "no such file,please check the file name!" <<endl;
- exit(0);
- }
- fin>>njob;
- for(int i = 1; i <= njob; i++)
- for (int j = 1; j <= njob; j++)
- fin >> c[i][j];
- }
- //-----------------------------------------------------------------------
- //读入工人、作业数njob,开销矩阵c[][N]
- //-----------------------------------------------------------------------
- void initData(int* task, int* bestSolution, int* worker, int& njob, int c[][N])
- {
- input(njob,c);
- for(int k =0; k <= njob; k++)
- {
- //设置每个人任务由不同工人承担时的费用及全局数组的初值
- worker[k] = 0;
- task[k] = 0;
- bestSolution[k] = 0;
- }
- }
- //-----------------------------------------------------------------------
- //测试:输出c[][N]
- //-----------------------------------------------------------------------
- void outputInitData(int c[][N])
- {
- for(int i = 1; i <= njob; i++)
- {
- for (int j = 1; j <= njob; j++)
- cout << c[i][j] <<" ";
- cout<<endl;
- }
- }
- //-----------------------------------------------------------------------
- //k: 当前要分配的任务,cost:当前作业分配后的花费
- //-----------------------------------------------------------------------
- void assign(int k,unsigned int cost)
- {
- int i;
- if(k > njob && cost < mincost)
- {
- mincost = cost;
- for(i=1; i<=njob; i++)
- bestSolution[i] = task[i];
- }
- else
- {
- for(i=1;i<=njob;i++) //分配任务k
- {
- if(worker[i]==0 && cost+c[k][i])
- {
- worker[i] = 1; task[k] = i;
- assign(k+1,cost+c[k][i]);
- worker[i] = 0; task[k] = 0;
- }
- }
- }
- }
- //-----------------------------------------------------------------------
- //输出结果:mincost、任务分配情况
- //-----------------------------------------------------------------------
- void outputResult(int minCost,int njob, int* bestSolution)
- {
- cout<<"最小总费用:"<<mincost<<endl;
- for(int i=1; i<= njob;i++)
- cout<< "任务:"<< i <<" 由工人:"<< bestSolution[i] << "执行 " <<endl;
- }
- //-----------------------------------------------------------------------
- void main()
- {
- initData(task, bestSolution, worker, njob, c);
- assign(1,0); //从任务1开始分配
- outputResult(mincost, njob,bestSolution);
- for(int i = 1; i <= njob; i++)
- cout << bestSolution[i] << " ";
- cout <<endl;
- }
- //=======================================================================