1.题目描述:设有n件工作分配给n个人。将工作j分配给第i个人所需的
费用为Cij。设计一个算法,使每个人都有一件不同的工作,并使总费用最少。
数据输入:第一行有一个正整数n,接下来是N行,每行N个数,表示工作费用。
结果输出:将计算最小费用输出。
2.问题分析:
常规思想:使用穷举法搜索,算法时间复杂度为N!,但此法采用分枝限界 来求解该问题,在不对实际问题求解的情况下,如何求出一个最优成本的
下界呢?很显然,任何解的成本都不会小于矩阵每一行的最小元素的和。不同于回溯法总是生成最近一个有希望节点的单个子女。分枝限界法总是扩展最有希望的子结点。如果能找到一个最小下界(搜索最小值的情况下)或最大上界(搜索最大值的情况下),那么在搜索时,总是扩展小于最小下界或者大于最大上界的结点。当求得的值小于所有未扩展结点的最小下界或者大于所有未扩展结点的最大上界时,停上搜索。
在此问题中,是求费用的最小值,只要能找出扩展结点的最小下界,就可进行剪枝。但怎样求最小下界呢?可以明任何可行解都不可能大于每一行最小元素的和。因此将每一行最小元素的各作为此搜索算法的一个下界,然后进行剪枝。对扩展的结点求出下界,对有最小下界的节点扩充,一直搜索到N层,如果第N层有最小下界的节点值小于所有未扩展的节点,则停止搜索。否则,扩展那些最小下界值小于当前节点最小下界的节点。本算法采用了最小堆,每次将新生成的节点压堆,然后扩展堆的根节点。如果根节点已经是第N层的节点,搜索停止。
3.代码:
void CWorkAllocation::Run(){
for (int i=0;i<m_nCount;++i){ //新建初始节点,将第一个工作分别分配给(0-N)个人
CTask* pAllocation = new CTask;
pAllocation->AllocateTask(i);
int lb = CalculateLowBound(pAllocation->GetAllocationState()); //计算最小下界
pAllocation->SetLowBound(lb);
m_Allocation.push_back(pAllocation);
}
make_heap(m_Allocation.begin(),m_Allocation.end(),Less); //设置最小堆
CTask* pA = GetMiniNode(); //取下界最小的节点并删除
while(pA->GetAllocationNum() < m_nCount){ //直到工作分配完成
int numAllocated = pA->GetAllocationNum();
const vector<int> AllocationState = pA->GetAllocationState();
bool* person = new bool[m_nCount];
memset(person,false,sizeof(bool)*m_nCount);
for (int i=0;i<AllocationState.size();++i) //将已经分配过的任务的人标记
person[AllocationState[i]] = true;
for (int i=numAllocated;i<m_nCount;++i){//扩展pA的叶子节点
CTask* pTask = new CTask(*pA);
for(int j=0;j<m_nCount;++j)
if (!person[j]){ //没分配过任务
person[j] =1;
pTask->AllocateTask(j);
int lb = CalculateLowBound(pTask->GetAllocationState()); //最小下界
pTask->SetLowBound(lb);
break;
}
m_Allocation.push_back(pTask);
push_heap(m_Allocation.begin(),m_Allocation.end(),Less);
}
delete []person;
delete pA;
pA = GetMiniNode(); //取下界最小的节点并删除
}
m_nMiniCost = pA->GetLowBound();
delete pA;
}
4.算法复杂度分析:
最好的情况下,只需要每一层搜索一次。时间复杂度为线性 O(n),最坏的情况每个节点都被扩充。时间复杂度为O(n!);