指派问题、回溯法

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

  1. void work(int i,int count){     
  2.   if(i>n)     
  3.     return ;     
  4.   for(int j=1;j<=n;j++)     
  5.     if(x[j] == 0){     
  6.       x[j] = 1;     
  7.       work(i+1,count+c[i][j]);     
  8.       x[j] = 0;     
  9.     }     
  10. }    

 

那么在这里,用回溯法的思想就是,首先分配的工作是: 
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

  1. #include<iostream>  
  2. using namespace std;  
  3.   
  4. int n,cost=0;  
  5. int x[100],c[100][100];  
  6.   
  7. void work(int i,int count){  
  8.     if(i>n && count<cost){  
  9.       cost = count;  
  10.       return ;  
  11.     }  
  12.     if(count<cost)  
  13.       for(int j=1;j<=n;j++)  
  14.         if(x[j] == 0){    
  15.           x[j] = 1;    
  16.           work(i+1,count+c[i][j]);    
  17.           x[j] = 0;    
  18.          }  
  19. }  
  20.   
  21. int main(){  
  22.     cin>>n;  
  23.     for(int i=1;i<=n;i++){  
  24.       for(int j=1;j<=n;j++){  
  25.         cin>>c[i][j];  
  26.         x[j] = 0;    
  27.       }  
  28.       cost+=c[i][i];    
  29.     }  
  30.     work(1,0);  
  31.     cout<<cost<<endl;  
  32.     system(“pause”);  
  33.     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");
}

    原文作者:回溯法
    原文地址: https://blog.csdn.net/llw01/article/details/8829097
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞