分别用蛮力法、回溯法、分支限界法求解任务分配问题。

任务分配问题描述:

假设有n个任务需要分配给n个人执行,每个人只执行一个任务,每个任务只由一个人执行。第i个人执行第j个任务的成本是Cij(1<=i,j<=n), 求解初最小成本的分配方案。

一、简单算法

最简单的算法是:蛮力法,这里采用增量穷举法求出所有的分配方案ps,再计算出每种方案的成本,比较求出最小成本的方案,即最优方案。

1)实现代码

#include<iostream>
#include<vector>
using namespace std;
#define INF 9999		//最大成本值
#define MAXN 21		
int n;
int c[MAXN][MAXN];
vector<vector<int>> ps;
void Insert(vector<int> s, int i, vector<vector<int>> &ps1) {
	//在每个集合元素中插入i得到ps1
	vector<int> s1;
	vector<int>::iterator it;
	for (int j = 0; j < i; j++)		//在s的每个位置都插入i
	{
		s1 = s;
		it = s1.begin() + j;		//求出插入位置
		s1.insert(it, i);			//插入整数i
		ps1.push_back(s1);			//添加到ps1中
	}
}

void Perm(int n)
{
	vector<vector<int>> ps1;		//临时存放子排列
	vector<vector<int>>::iterator it;		//全排列迭代器
	vector<int> s, s1;
	s.push_back(1);
	ps.push_back(s);				//添加{1}集合元素
	for (int i = 2; i <= n; i++)	//循环添加1~n
	{
		ps1.clear();				//ps1存放插入i的结果
		for (it = ps.begin(); it != ps.end(); ++it)
		{
			Insert(*it, i, ps1);	//在每个集合元素中间插入i得到ps1
		}
		ps = ps1;
	}
}
void Allocate(int n, int& mini, int& minc)		//求任务分配问题的最优方案
{
	Perm(n);
	for (int i = 0; i < ps.size(); i++)			//求出每个方案的成本
	{
		int cost = 0;
		for (int j = 0; j < ps[i].size(); j++)
		{
			cost += c[j][ps[i][j] - 1];
		}
		if (cost < minc)
		{
			minc = cost;
			mini = i;
		}
	}
}
int main()
{
	int mincost = INF, mini;
	cin >> n;
	for (int i = 0; i < n; i++) 
		for (int j = 0; j < n; j++) 
			cin >> c[i][j];
	Allocate(n, mini, mincost);
	printf("最优方案:\n");
	for (int k = 0; k < ps[mini].size(); k++)
	{
		printf("第%d个人安排任务%d\n", k + 1, ps[mini][k]);
	}
	printf("总成本=%d\n", mincost);
	return 0;
}

2)运行结果图

飒飒

3)复杂度分析

蛮力法求解任务分配问题的时间复杂度为:O( n x n!)

二、回溯算法

1)实现代码

#include<iostream>
#include<queue>
#include<vector>
using namespace std;
#define INF 9999
#define MAXN 21
int n;
int c[MAXN][MAXN];
int x[MAXN];		//临时解
int cost = 0;		//临时解的成本
int bestx[MAXN];	//最优解
int mincost = INF;	//最优解的成本
bool worker[MAXN];	//worker[j]表示任务j是否已经分配人员
void allocate(int i)	//为第i个人员分配任务
{
	if (i > n)			//到达叶子节点
	{
		if (cost < mincost)
		{
			mincost = cost;
			for (int j = 1; j <= n; j++)
				bestx[j] = x[j];
		}
	}
	else
	{
		for (int j = 1; j <= n; j++) {	//为人员i试探任务j分配情况,从1到n
			if (!worker[j])				//任务j还没分配
			{
				worker[j] = true;
				x[i] = j;				//任务j分配给i人员
				cost += c[i][j];
				allocate(i + 1);		//为人员i+1分配任务
				worker[j] = false;		//回溯
				x[i] = 0;
				cost -= c[i][j];
			}
		}
	}
}
int main() {
	memset(worker, 0, sizeof(worker));
	cin >> n;
	for (int i = 1; i <=n; i++)
		for (int j = 1; j <= n; j++)
			cin >> c[i][j];
	allocate(1);		//从人员1开始尝试分配
	
	printf("最优方案:\n");
	for (int k = 1; k <= n; k++)
	{
		printf("第%d个人安排任务%d\n", k ,bestx[k]);
	}
	printf("总成本=%d\n", mincost);
	return 0;
}

2)运行结果图

在这里插入图片描述

3)复杂度分析

回溯算法求解任务分配问题中,每个人员试探1~n个任务,对应的解空间树是一颗n叉树,算法时间复杂度为:O(n^n)

三、分支限界算法

这里采用队列式的分治限界法求解。

1)实现代码

#include<iostream>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f			//定义无穷大
#define MAXN 21
int n;
int c[MAXN][MAXN];				//人员任务-成本矩阵
int bestx[MAXN];				//最优分配方案
int mincost = INF;				//最小成本
int total = 1;					//结点个数累计
struct NodeType									//队列结点类型
{
	int no;										//结点编号
	int i;										//人员编号				
	int x[MAXN];								//x[i]为人员i分配的任务编号
	bool worker[MAXN];							//worker[i]=true表示任务i已经被分配
	int cost;									//当前已分配任务的总成本
	int lb;										//下界
	bool operator<(const NodeType& s) const		//重载<关系函数>
	{
		return lb > s.lb;
	}
};
void bound(NodeType& e) {						//求结点e的界限值
	int minsum = 0;
	for (int i = e.i + 1; i <= n; i++) {		//求e[e.i+1...n]行中的最小元素
		int minc = INF;
		for (int j = 1; j <= n; j++) 
			if (e.worker[j] == false && c[i][j] < minc)
				minc = c[i][j];
		minsum += minc;
		
	}
	e.lb = e.cost + minsum;
}
void bfs() {
	int j;
	NodeType e, e1;
	priority_queue<NodeType>qu;
	memset(e.x, 0, sizeof(e.x));					//初始化根节点的x值
	memset(e.worker, 0, sizeof(e.worker));			//初始化根节点的worker
	e.i = 0;
	e.cost = 0;
	bound(e);										//求根节点的lb		
	e.no = total++;
	qu.push(e);										//根节点进队列
	while (!qu.empty()) {
		e = qu.top(); qu.pop();						//弹出队列结点e,当前考虑人员e.i
		if (e.i == n) {								//比较求最优解
			if (e.cost < mincost) {
				mincost = e.cost;
				for (j = 1; j <= n; j++)
					bestx[j] = e.x[j];
			}
		}
		e1.i = e.i + 1;								//拓展分配下一个人员的任务,对应结点e1
		for (j = 1; j <= n; j++) {					//考虑n个任务
			if (e.worker[j])						//任务j是否已经分配人员,若已分配则跳过
				continue;
			for (int i = 1; i <= n; i++) {
				e1.x[i] = e.x[i];
			}
			e1.x[e1.i] = j;							//为人员e1.i分配任务j
			for (int k = 1; k <= n; k++) {			
				e1.worker[k] = e.worker[k];
			}
			e1.worker[j] = true;					//表示任务j已经分配
			e1.cost = e.cost + c[e1.i][j];
			bound(e1);								//求e1的lb
			e1.no = total++;
			if (e1.lb <= mincost)					//剪枝		
				qu.push(e1);
		}
	}
}
int main() {
	cout << "输入人员数/任务数的大小:n" << endl;
	cin >> n;
	cout << "输入人员-任务成本值("<<n<<"x"<<n<<"矩阵):" << endl;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			cin >> c[i][j];
		}
	}
	bfs();
	printf("最优方案:\n");
	for (int k = 1; k <= n; k++)
		printf("第%d个人员分配第%d个任务\n",k, bestx[k]);
	cout <<"总成本="<< mincost;
	return 0;
}

2)运行结果图

在这里插入图片描述

3)复杂度分析

分支限界算法求解任务分配问题的时间复杂度为:O(n^n)

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐