工作分配问题
题目
设有n件工作分配给n个人。将第i份工作分配给第j个人所需的费用为c。试设计一个算法,为每一个人都分配1件不同的工作,并使总费用达到最小。
分析
深度优先搜索(dfs)
可以将本题视为人选工作或者工作“选人”,可以用一棵二叉树例举出所有情况,树的高度即表示第几份工作,每一个节点即表示一个人。
思路
- dfs:深度遍历二叉树,当到达叶子节点时,如果该路径为最短路径则更新最小代价并更新最短路径。
- 回溯:在搜索过程中,如果发现当前分配方案不能导致更优解,需要撤销(回溯)之前的分配决定,重新为该人分配其他任务。(用st数组来存是否被使用)
优化
- 剪枝:进行到某一步时,如果其代价已经大于最小代价,则不再需要计算该二叉树分支以下的部分,即进行剪枝。
- 记录路径:每次用一个数组(每次重新记录路径时会从数组起始下标记录,会不断覆盖原数组)去记录当前方案的路径,当数组记录完成最后一步时,将该路径记录到最短路径中(即更新最短路径)。
程序设计
- 数组说明
- g 表示代价矩阵
- st 用来标记某一个人是否被选择,确保每个人只被分配一次任务,因为问题要求为每个人分配一个不同的任务。
- n 工作及工人的数量
- Min 最小代价
- sum 计算过程中的代价和
- s 计算过程中记录路径
- p 最短路径
- 代码分析
#include <iostream>
using namespace std;
const int N = 110;
int g[N][N];
int st[N];
int n, Min;
int sum;
int s[N] ,p[N];
void dfs(int x) //x表示树的第x层
{
if(x > n) //遍历到叶子节点
{
if(sum < Min)
{
Min = sum; //更新最小代价
for(int i = 1; i <= n; i++) p[i] = s[i]; //更新最短路径
return;
}
}
for(int i = 1; i <= n; i++) //遍历截止到第i层未被选择的人
{
if(!st[i]) //如果该点未被使用
{
st[i] = 1; //标记第i个人已经被“选”
sum += g[x][i]; //更新代价
s[x] = i; //记录路径
if(sum < Min) dfs(x + 1); //进行剪枝
st[i] = 0; //回溯-撤销之前的分配
sum -= g[x][i]; //回溯-撤销之前的分配
}
}
}
int main()
{
Min = 1e8; //初始化Min为无穷大
cin >> n;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
cin >> g[i][j]; //初始化代价矩阵
dfs(1); //从第一层开始遍历
cout << "分配关系为:" << endl;
int cnt = 1;
for(int i = 1; i <= n; i++) cout << "第" << cnt++ <<"个任务应该分配给第" << p[i] << " 个人" << endl;
cout << endl;
cout << "所需最少时间为:" << Min;
return 0;
}